@runeditrun/rer 0.0.0-stage → 0.1.1

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 (527) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -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 +182 -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 +158 -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 +117 -0
  71. package/global/launch/facts.mjs +94 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +63 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +106 -0
  77. package/global/launch/steps.mjs +90 -0
  78. package/global/launch/stop.mjs +89 -0
  79. package/global/launch/worker.mjs +62 -0
  80. package/global/list.mjs +59 -0
  81. package/global/main.mjs +53 -0
  82. package/global/package-info.mjs +38 -0
  83. package/global/registry.mjs +24 -0
  84. package/global/rer.mjs +29 -0
  85. package/global/update/archive.mjs +89 -0
  86. package/global/update/command.mjs +90 -0
  87. package/global/update/latest.mjs +6 -0
  88. package/global/update/notice.mjs +58 -0
  89. package/global/update/registry.mjs +47 -0
  90. package/global/version.mjs +29 -0
  91. package/install.sh +144 -0
  92. package/modules/ai/LICENSE +21 -0
  93. package/modules/ai/README.md +90 -0
  94. package/modules/ai/client.mjs +71 -0
  95. package/modules/ai/config.mjs +39 -0
  96. package/modules/ai/dev/index.d.mts +6 -0
  97. package/modules/ai/dev/index.mjs +64 -0
  98. package/modules/ai/dev/outbox.mjs +33 -0
  99. package/modules/ai/errors.mjs +28 -0
  100. package/modules/ai/fake/index.d.mts +3 -0
  101. package/modules/ai/fake/index.mjs +82 -0
  102. package/modules/ai/index.d.mts +10 -0
  103. package/modules/ai/index.mjs +3 -0
  104. package/modules/ai/package.json +6 -0
  105. package/modules/container/README.md +156 -0
  106. package/modules/container/adapters/d1.mjs +34 -0
  107. package/modules/container/adapters/egress.mjs +40 -0
  108. package/modules/container/adapters/jobs.mjs +47 -0
  109. package/modules/container/adapters/objects.mjs +84 -0
  110. package/modules/container/adapters/retained.mjs +14 -0
  111. package/modules/container/adapters/sqlite-error.mjs +7 -0
  112. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  113. package/modules/container/assets.mjs +14 -0
  114. package/modules/container/bindings.mjs +1 -0
  115. package/modules/container/boundary.mjs +38 -0
  116. package/modules/container/build.mjs +65 -0
  117. package/modules/container/deploy-input.mjs +17 -0
  118. package/modules/container/entrypoint.sh +5 -0
  119. package/modules/container/host.mjs +43 -0
  120. package/modules/container/ingress.mjs +23 -0
  121. package/modules/container/lock.mjs +11 -0
  122. package/modules/container/migrations.mjs +16 -0
  123. package/modules/container/node-engine.mjs +39 -0
  124. package/modules/container/portability-schema.mjs +14 -0
  125. package/modules/container/remote/deploy.py +76 -0
  126. package/modules/container/remote/files.py +275 -0
  127. package/modules/container/remote/resources.py +157 -0
  128. package/modules/container/runner.mjs +10 -0
  129. package/modules/container/ssh-plan.mjs +44 -0
  130. package/modules/container/ssh.mjs +63 -0
  131. package/modules/container/transfer.mjs +27 -0
  132. package/modules/container/worker-engine.mjs +32 -0
  133. package/modules/dependencies/README.md +211 -0
  134. package/modules/dependencies/approval.mjs +51 -0
  135. package/modules/dependencies/auth.mjs +40 -0
  136. package/modules/dependencies/authority.mjs +14 -0
  137. package/modules/dependencies/crypto.mjs +13 -0
  138. package/modules/dependencies/index.d.mts +23 -0
  139. package/modules/dependencies/index.mjs +113 -0
  140. package/modules/dependencies/logout.mjs +42 -0
  141. package/modules/dependencies/native.mjs +9 -0
  142. package/modules/dependencies/protocol.mjs +23 -0
  143. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  144. package/modules/dependencies/schema/dependencies.sql +15 -0
  145. package/modules/dependencies/store.mjs +19 -0
  146. package/modules/deploy-kit/README.md +308 -0
  147. package/modules/deploy-kit/artifact.mjs +47 -0
  148. package/modules/deploy-kit/build-modules.mjs +21 -0
  149. package/modules/deploy-kit/check.mjs +13 -0
  150. package/modules/deploy-kit/cli.mjs +50 -0
  151. package/modules/deploy-kit/container-config.mjs +8 -0
  152. package/modules/deploy-kit/default-stage.mjs +14 -0
  153. package/modules/deploy-kit/deploy.mjs +80 -0
  154. package/modules/deploy-kit/health-request.mjs +86 -0
  155. package/modules/deploy-kit/health.mjs +99 -0
  156. package/modules/deploy-kit/index.mjs +12 -0
  157. package/modules/deploy-kit/local-only.mjs +10 -0
  158. package/modules/deploy-kit/migrations.mjs +48 -0
  159. package/modules/deploy-kit/owner-cli.mjs +63 -0
  160. package/modules/deploy-kit/owner.mjs +297 -0
  161. package/modules/deploy-kit/prepare.mjs +62 -0
  162. package/modules/deploy-kit/provider.mjs +99 -0
  163. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  164. package/modules/deploy-kit/receipt.mjs +16 -0
  165. package/modules/deploy-kit/render.mjs +47 -0
  166. package/modules/deploy-kit/resources.mjs +106 -0
  167. package/modules/deploy-kit/service-grants.mjs +25 -0
  168. package/modules/deploy-kit/sql-safety.mjs +94 -0
  169. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  170. package/modules/deploy-kit/teardown.mjs +100 -0
  171. package/modules/deploy-kit/templates.mjs +31 -0
  172. package/modules/deploy-kit/validation.mjs +58 -0
  173. package/modules/deploy-kit/worker.mjs +105 -0
  174. package/modules/erasure/CONTRACT.md +90 -0
  175. package/modules/erasure/README.md +221 -0
  176. package/modules/erasure/audit.mjs +23 -0
  177. package/modules/erasure/blobs.mjs +45 -0
  178. package/modules/erasure/cascade.mjs +91 -0
  179. package/modules/erasure/completion.mjs +62 -0
  180. package/modules/erasure/declaration.mjs +51 -0
  181. package/modules/erasure/fence.mjs +19 -0
  182. package/modules/erasure/index.d.mts +62 -0
  183. package/modules/erasure/index.mjs +85 -0
  184. package/modules/erasure/order.mjs +56 -0
  185. package/modules/erasure/plan.mjs +121 -0
  186. package/modules/erasure/rate.mjs +57 -0
  187. package/modules/erasure/receipt.mjs +32 -0
  188. package/modules/erasure/root.mjs +21 -0
  189. package/modules/erasure/schema/erasure-rates.sql +5 -0
  190. package/modules/erasure/schema/erasure.sql +27 -0
  191. package/modules/erasure/scope.mjs +29 -0
  192. package/modules/erasure/sql.mjs +23 -0
  193. package/modules/erasure/verification.mjs +69 -0
  194. package/modules/integrations/core/CONTRACT.md +50 -0
  195. package/modules/integrations/core/README.md +27 -0
  196. package/modules/integrations/core/crypto.ts +19 -0
  197. package/modules/integrations/core/database.ts +21 -0
  198. package/modules/integrations/core/delivery.ts +97 -0
  199. package/modules/integrations/core/destination.ts +13 -0
  200. package/modules/integrations/core/index.ts +7 -0
  201. package/modules/integrations/core/node-transport.ts +72 -0
  202. package/modules/integrations/core/public-address.ts +57 -0
  203. package/modules/integrations/core/repository.ts +74 -0
  204. package/modules/integrations/core/schema.sql +36 -0
  205. package/modules/integrations/core/sqlite.ts +20 -0
  206. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  207. package/modules/integrations/core/tokens.ts +71 -0
  208. package/modules/integrations/core/types.ts +52 -0
  209. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  210. package/modules/integrations/core/webhook.ts +73 -0
  211. package/modules/job-queue/README.md +173 -0
  212. package/modules/job-queue/cli.mjs +64 -0
  213. package/modules/job-queue/command.mjs +64 -0
  214. package/modules/job-queue/config.mjs +48 -0
  215. package/modules/job-queue/identity.mjs +48 -0
  216. package/modules/job-queue/index.mjs +135 -0
  217. package/modules/job-queue/membership.mjs +30 -0
  218. package/modules/job-queue/module.json +10 -0
  219. package/modules/job-queue/policy.mjs +29 -0
  220. package/modules/job-queue/processes.mjs +80 -0
  221. package/modules/job-queue/resources.mjs +37 -0
  222. package/modules/job-queue/storage.mjs +64 -0
  223. package/modules/job-queue/supervisor.mjs +30 -0
  224. package/modules/job-queue/waiting.mjs +16 -0
  225. package/modules/mail/LICENSE +21 -0
  226. package/modules/mail/README.md +336 -0
  227. package/modules/mail/doctor.mjs +81 -0
  228. package/modules/mail/index.mjs +8 -0
  229. package/modules/mail/live-proof/README.md +113 -0
  230. package/modules/mail/live-proof/run.mjs +55 -0
  231. package/modules/mail/package.json +19 -0
  232. package/modules/mail/pnpm-lock.yaml +537 -0
  233. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  234. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  235. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  236. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  237. package/modules/mail/src/adapters/messaging/README.md +92 -0
  238. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  239. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  240. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  241. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  242. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  243. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  244. package/modules/mail/src/adapters/resend/README.md +120 -0
  245. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  246. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  247. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  248. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  249. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  250. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  251. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  252. package/modules/mail/src/core/errors.mjs +9 -0
  253. package/modules/mail/src/core/transport.mjs +33 -0
  254. package/modules/mail/src/core/validation.mjs +172 -0
  255. package/modules/mail/src/development/index.d.mts +37 -0
  256. package/modules/mail/src/development/index.mjs +35 -0
  257. package/modules/mail/src/development/outbox.mjs +78 -0
  258. package/modules/mail/src/development/schema.sql +9 -0
  259. package/modules/mail/src/development/viewer-html.mjs +42 -0
  260. package/modules/mail/src/development/viewer.mjs +65 -0
  261. package/modules/mail/src/doctor/README.md +107 -0
  262. package/modules/mail/src/doctor/common.mjs +64 -0
  263. package/modules/mail/src/doctor/dns.mjs +102 -0
  264. package/modules/mail/src/doctor/index.mjs +28 -0
  265. package/modules/mail/src/doctor/probe.mjs +66 -0
  266. package/modules/mail/src/doctor/receive.mjs +81 -0
  267. package/modules/mail/src/doctor/records.mjs +45 -0
  268. package/modules/mail/src/events/README.md +268 -0
  269. package/modules/mail/src/events/d1-schema.sql +25 -0
  270. package/modules/mail/src/events/d1-storage.mjs +76 -0
  271. package/modules/mail/src/events/index.mjs +3 -0
  272. package/modules/mail/src/events/intake.mjs +201 -0
  273. package/modules/mail/src/events/storage.mjs +52 -0
  274. package/modules/mail/src/events/verify.mjs +92 -0
  275. package/modules/mail/src/inbound/README.md +137 -0
  276. package/modules/mail/src/inbound/bytes.mjs +91 -0
  277. package/modules/mail/src/inbound/headers.mjs +143 -0
  278. package/modules/mail/src/inbound/index.mjs +29 -0
  279. package/modules/mail/src/inbound/mime.mjs +36 -0
  280. package/modules/mail/src/inbound/multipart.mjs +27 -0
  281. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  282. package/modules/mail/src/inbound/resend.mjs +54 -0
  283. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  284. package/modules/mail/src/sender.mjs +33 -0
  285. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  286. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  287. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  288. package/modules/operator-auth/README.md +599 -0
  289. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  290. package/modules/operator-auth/accounts/admin.mjs +159 -0
  291. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  292. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  293. package/modules/operator-auth/accounts/index.d.mts +9 -0
  294. package/modules/operator-auth/accounts/index.mjs +9 -0
  295. package/modules/operator-auth/accounts/native.d.mts +38 -0
  296. package/modules/operator-auth/accounts/native.mjs +171 -0
  297. package/modules/operator-auth/accounts/oa.mjs +90 -0
  298. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  299. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  300. package/modules/operator-auth/accounts/roles.mjs +26 -0
  301. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  302. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  303. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  304. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  305. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  306. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  307. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  308. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  309. package/modules/operator-auth/accounts/store.mjs +246 -0
  310. package/modules/operator-auth/branding.mjs +66 -0
  311. package/modules/operator-auth/config.mjs +35 -0
  312. package/modules/operator-auth/conformance/README.md +81 -0
  313. package/modules/operator-auth/conformance/index.d.mts +43 -0
  314. package/modules/operator-auth/conformance/index.mjs +300 -0
  315. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  316. package/modules/operator-auth/crypto.mjs +17 -0
  317. package/modules/operator-auth/dev/badge.mjs +62 -0
  318. package/modules/operator-auth/dev/index.d.mts +14 -0
  319. package/modules/operator-auth/dev/index.mjs +137 -0
  320. package/modules/operator-auth/dev/personas.mjs +17 -0
  321. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  322. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  323. package/modules/operator-auth/dev-admission.d.mts +4 -0
  324. package/modules/operator-auth/dev-admission.mjs +10 -0
  325. package/modules/operator-auth/dev-gate.mjs +86 -0
  326. package/modules/operator-auth/docs/README.md +78 -0
  327. package/modules/operator-auth/docs/native-accounts.md +313 -0
  328. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  329. package/modules/operator-auth/docs/vite-spa.md +144 -0
  330. package/modules/operator-auth/errors.mjs +9 -0
  331. package/modules/operator-auth/http.mjs +42 -0
  332. package/modules/operator-auth/identity/admission.mjs +105 -0
  333. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  334. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  335. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  336. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  337. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  338. package/modules/operator-auth/identity/index.d.mts +7 -0
  339. package/modules/operator-auth/identity/index.mjs +5 -0
  340. package/modules/operator-auth/identity/oidc/README.md +171 -0
  341. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  342. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  343. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  344. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  345. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  346. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  347. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  348. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  349. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  350. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  351. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  352. package/modules/operator-auth/identity/principal.mjs +28 -0
  353. package/modules/operator-auth/index.d.mts +223 -0
  354. package/modules/operator-auth/index.mjs +11 -0
  355. package/modules/operator-auth/input.mjs +55 -0
  356. package/modules/operator-auth/local-cli.mjs +16 -0
  357. package/modules/operator-auth/page.mjs +34 -0
  358. package/modules/operator-auth/policy.mjs +20 -0
  359. package/modules/operator-auth/routes.mjs +91 -0
  360. package/modules/operator-auth/service.mjs +152 -0
  361. package/modules/operator-auth/signin/index.mjs +67 -0
  362. package/modules/owner-claim/README.md +137 -0
  363. package/modules/owner-claim/idp.mjs +40 -0
  364. package/modules/owner-claim/index.d.mts +27 -0
  365. package/modules/owner-claim/index.mjs +39 -0
  366. package/modules/owner-claim/page.mjs +11 -0
  367. package/modules/owner-claim/schema/idp.sql +4 -0
  368. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  369. package/modules/owner-claim/transaction.mjs +87 -0
  370. package/modules/owner-claim/verify.mjs +80 -0
  371. package/modules/portability/README.md +444 -0
  372. package/modules/portability/bridge.mjs +102 -0
  373. package/modules/portability/catalogue.mjs +118 -0
  374. package/modules/portability/cli-options.mjs +38 -0
  375. package/modules/portability/cli.mjs +56 -0
  376. package/modules/portability/copies.mjs +68 -0
  377. package/modules/portability/dev-data.mjs +32 -0
  378. package/modules/portability/diff.mjs +33 -0
  379. package/modules/portability/export.mjs +129 -0
  380. package/modules/portability/files.mjs +65 -0
  381. package/modules/portability/format.mjs +122 -0
  382. package/modules/portability/generated.mjs +60 -0
  383. package/modules/portability/hash.mjs +79 -0
  384. package/modules/portability/http.mjs +42 -0
  385. package/modules/portability/import.mjs +185 -0
  386. package/modules/portability/index.d.mts +58 -0
  387. package/modules/portability/index.mjs +22 -0
  388. package/modules/portability/journal.mjs +49 -0
  389. package/modules/portability/legacy-files.mjs +43 -0
  390. package/modules/portability/legacy-safety.mjs +27 -0
  391. package/modules/portability/lines.mjs +36 -0
  392. package/modules/portability/migration-history.mjs +18 -0
  393. package/modules/portability/migration-statements.mjs +23 -0
  394. package/modules/portability/payloads.mjs +52 -0
  395. package/modules/portability/provider-data.mjs +25 -0
  396. package/modules/portability/restore-safety.mjs +30 -0
  397. package/modules/portability/schema/portability.sql +25 -0
  398. package/modules/portability/storage.mjs +49 -0
  399. package/modules/release/README.md +212 -0
  400. package/modules/release/catalogue.mjs +79 -0
  401. package/modules/release/export.mjs +51 -0
  402. package/modules/release/produce.mjs +136 -0
  403. package/modules/release/public-child-transport-state.cjs +29 -0
  404. package/modules/release/public-child.mjs +71 -0
  405. package/modules/release/public-git.mjs +21 -0
  406. package/modules/release/publication-scan.mjs +22 -0
  407. package/modules/release/publish-handoff.mjs +37 -0
  408. package/modules/release/source.mjs +71 -0
  409. package/modules/release/test-summary.mjs +50 -0
  410. package/onboarding/README.md +163 -0
  411. package/onboarding/activity.mjs +31 -0
  412. package/onboarding/adapter-auth.mjs +88 -0
  413. package/onboarding/adapter-hosting.mjs +35 -0
  414. package/onboarding/adapter-labels.mjs +13 -0
  415. package/onboarding/adapter-settings.mjs +129 -0
  416. package/onboarding/adapter.mjs +238 -0
  417. package/onboarding/agent-handoff.mjs +9 -0
  418. package/onboarding/app-processes.mjs +21 -0
  419. package/onboarding/archive-output.d.mts +3 -0
  420. package/onboarding/archive-output.mjs +54 -0
  421. package/onboarding/command-custody.mjs +24 -0
  422. package/onboarding/command-recovery.mjs +8 -0
  423. package/onboarding/commands.mjs +166 -0
  424. package/onboarding/dependency-approval.mjs +36 -0
  425. package/onboarding/dev-identity.mjs +23 -0
  426. package/onboarding/email.mjs +24 -0
  427. package/onboarding/failures.mjs +42 -0
  428. package/onboarding/input-limits.mjs +7 -0
  429. package/onboarding/installer-inputs.mjs +24 -0
  430. package/onboarding/launcher.mjs +34 -0
  431. package/onboarding/local/build-files.mjs +64 -0
  432. package/onboarding/local/build.mjs +49 -0
  433. package/onboarding/local/environment.mjs +30 -0
  434. package/onboarding/local/files.mjs +35 -0
  435. package/onboarding/local/operation.mjs +166 -0
  436. package/onboarding/local/owner.mjs +34 -0
  437. package/onboarding/local/process-table.mjs +114 -0
  438. package/onboarding/local/recovery.mjs +79 -0
  439. package/onboarding/local/state.mjs +31 -0
  440. package/onboarding/metadata.mjs +7 -0
  441. package/onboarding/operator-install.mjs +45 -0
  442. package/onboarding/operator-installer.mjs +39 -0
  443. package/onboarding/operator-prerequisites.mjs +27 -0
  444. package/onboarding/package.json +6 -0
  445. package/onboarding/prefill.mjs +16 -0
  446. package/onboarding/private-files.mjs +97 -0
  447. package/onboarding/private.mjs +35 -0
  448. package/onboarding/process-cleanup.mjs +134 -0
  449. package/onboarding/process-diagnostics.mjs +91 -0
  450. package/onboarding/process-scope.mjs +118 -0
  451. package/onboarding/process-table.mjs +118 -0
  452. package/onboarding/process-tree.mjs +36 -0
  453. package/onboarding/production-modules.mjs +22 -0
  454. package/onboarding/production-options.mjs +40 -0
  455. package/onboarding/production-plan.mjs +8 -0
  456. package/onboarding/production-runtime.mjs +30 -0
  457. package/onboarding/project-vite.mjs +14 -0
  458. package/onboarding/providers.mjs +289 -0
  459. package/onboarding/queue.mjs +53 -0
  460. package/onboarding/queued-entry.mjs +34 -0
  461. package/onboarding/readiness.mjs +59 -0
  462. package/onboarding/redaction-jwt.mjs +32 -0
  463. package/onboarding/redaction.mjs +298 -0
  464. package/onboarding/schema/SETUP.schema.json +514 -0
  465. package/onboarding/schema/dependencies.mjs +35 -0
  466. package/onboarding/schema/json-schema.mjs +202 -0
  467. package/onboarding/schema/setup-validation.mjs +325 -0
  468. package/onboarding/secrets.mjs +21 -0
  469. package/onboarding/server.mjs +188 -0
  470. package/onboarding/setup-manifest.mjs +116 -0
  471. package/onboarding/state.mjs +46 -0
  472. package/onboarding/theme-files.mjs +37 -0
  473. package/onboarding/theme.mjs +118 -0
  474. package/onboarding/ui/activity.js +98 -0
  475. package/onboarding/ui/address-client.mjs +33 -0
  476. package/onboarding/ui/app.js +377 -0
  477. package/onboarding/ui/index.html +1 -0
  478. package/onboarding/ui/model-core.mjs +127 -0
  479. package/onboarding/ui/model.mjs +5 -0
  480. package/onboarding/ui/style.css +63 -0
  481. package/onboarding/ui/theme.css +37 -0
  482. package/onboarding/ui/workspace-art.js +30 -0
  483. package/onboarding/wrangler-auth.mjs +43 -0
  484. package/operations/README.md +246 -0
  485. package/operations/operations.mjs +168 -0
  486. package/operations/project-operations.mjs +157 -0
  487. package/package.json +11 -4
  488. package/scripts/publish-check/README.md +72 -0
  489. package/scripts/publish-check/checksum_maps.py +52 -0
  490. package/scripts/publish-check/cli.py +34 -0
  491. package/scripts/publish-check/command_arguments.py +51 -0
  492. package/scripts/publish-check/command_shell_quotes.py +41 -0
  493. package/scripts/publish-check/command_source_quotes.py +120 -0
  494. package/scripts/publish-check/content_identities.py +250 -0
  495. package/scripts/publish-check/copy_identities.py +41 -0
  496. package/scripts/publish-check/der.py +61 -0
  497. package/scripts/publish-check/destination.py +117 -0
  498. package/scripts/publish-check/env_values.py +74 -0
  499. package/scripts/publish-check/font_licence.py +23 -0
  500. package/scripts/publish-check/git_history.py +115 -0
  501. package/scripts/publish-check/github_refs.py +217 -0
  502. package/scripts/publish-check/gitleaks.py +72 -0
  503. package/scripts/publish-check/gitleaks.toml +40 -0
  504. package/scripts/publish-check/history_scan.py +176 -0
  505. package/scripts/publish-check/hit_location.py +18 -0
  506. package/scripts/publish-check/hygiene_config.py +42 -0
  507. package/scripts/publish-check/hygiene_text.py +11 -0
  508. package/scripts/publish-check/identity-recipe.mjs +54 -0
  509. package/scripts/publish-check/json_identity.py +79 -0
  510. package/scripts/publish-check/license-exceptions.json +11 -0
  511. package/scripts/publish-check/licenses.py +49 -0
  512. package/scripts/publish-check/policy.py +18 -0
  513. package/scripts/publish-check/project_identity.py +89 -0
  514. package/scripts/publish-check/provenance.py +91 -0
  515. package/scripts/publish-check/provenance_pins.py +33 -0
  516. package/scripts/publish-check/public_base.py +63 -0
  517. package/scripts/publish-check/public_hygiene.py +80 -0
  518. package/scripts/publish-check/publication-config.json +12 -0
  519. package/scripts/publish-check/publication_config.py +34 -0
  520. package/scripts/publish-check/report.py +52 -0
  521. package/scripts/publish-check/repository_commands.py +75 -0
  522. package/scripts/publish-check/repository_intent.py +52 -0
  523. package/scripts/publish-check/reviewed.py +41 -0
  524. package/scripts/publish-check/source_hashes.mjs +6 -0
  525. package/scripts/publish-check/tree_scan.py +88 -0
  526. package/scripts/publish-check/trust_bundle.py +87 -0
  527. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,80 @@
1
+ import { readFile, readdir, lstat } from 'node:fs/promises';
2
+ import { createHash } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { contained } from './project.mjs';
5
+
6
+ // SC-05.auth-copy: the operator-auth layers a seed copied from Base stay the
7
+ // bytes of the Base revision it pinned. A seed records its copies in
8
+ // base-copies.json (edition authoring) or composition.lock.json /
9
+ // foundation.lock.json (composed foundations); each records the SHA-256 of the
10
+ // Base bytes it copied. Two groups are checked on their own: the application,
11
+ // runtime and schema copies (one revision pins them all, since they are coupled)
12
+ // and the CLI's bundled tooling under scripts/base-cli (one revision of its
13
+ // own, which a CLI refresh moves without touching the application's auth). Each
14
+ // drifted, missing or unrecorded file in either group is named. A seed that
15
+ // imports Base in place has no copy.
16
+ const ORIGIN = 'modules/operator-auth';
17
+ const CLI_ROOT = 'scripts/base-cli';
18
+ const within = (path, prefix) => path === prefix || path.startsWith(`${prefix}/`);
19
+ const groupOf = path => typeof path === 'string' && within(path, CLI_ROOT) ? 'cli' : 'application';
20
+ const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
21
+ async function json(root, path) { try { return JSON.parse(await readFile(await contained(root, path), 'utf8')); } catch { return null; } }
22
+
23
+ // Every file under a copied directory, so an added file is named too.
24
+ async function walk(root, path) {
25
+ let stat; try { stat = await lstat(await contained(root, path)); } catch { return []; }
26
+ if (stat.isFile()) return [path];
27
+ if (!stat.isDirectory()) return [path];
28
+ const files = [];
29
+ for (const entry of await readdir(join(root, path), { withFileTypes: true })) files.push(...await walk(root, `${path}/${entry.name}`));
30
+ return files;
31
+ }
32
+
33
+ // [{ group, path, revision, files: { local path: sha256 } }] for every recorded auth copy.
34
+ async function recordedCopies(root) {
35
+ const copies = [];
36
+ const record = await json(root, 'base-copies.json');
37
+ for (const copy of record?.copies ?? []) {
38
+ if (typeof copy?.originPath === 'string' && within(copy.originPath, ORIGIN) && copy.files && typeof copy.files === 'object') copies.push({ path: copy.path, revision: copy.revision, files: copy.files, directory: copy.originPath === ORIGIN, group: groupOf(copy.path) });
39
+ }
40
+ for (const name of ['composition.lock.json', 'foundation.lock.json']) {
41
+ const lock = await json(root, name);
42
+ if (!lock?.files) continue;
43
+ const revision = lock.repositories?.base?.commit ?? lock.base?.commit;
44
+ for (const group of ['application', 'cli']) {
45
+ const entries = Object.entries(lock.files).filter(([path, file]) => groupOf(path) === group && typeof file?.source === 'string' && within(file.source, ORIGIN));
46
+ const files = Object.fromEntries(entries.filter(([, file]) => typeof file.sha256 === 'string').map(([path, file]) => [path, file.sha256]));
47
+ const directories = [...new Set(entries.filter(([, file]) => !file.source.endsWith('.sql')).map(([path, file]) => path.slice(0, path.length - (file.source.length - ORIGIN.length))))];
48
+ if (Object.keys(files).length) copies.push({ path: directories[0] ?? null, revision, files, directory: directories.length === 1, group });
49
+ }
50
+ }
51
+ return copies;
52
+ }
53
+
54
+ export async function authCopyClauses(root) {
55
+ const copies = await recordedCopies(root);
56
+ if (!copies.length) return [];
57
+ const clauses = [];
58
+ for (const [group, id, scope] of [['application', 'SC-05.auth-copy.pin', 'the application, runtime and schema copies'], ['cli', 'SC-05.auth-copy.cli-pin', `the CLI's bundled copy under ${CLI_ROOT}`]]) {
59
+ const grouped = copies.filter(copy => copy.group === group);
60
+ if (!grouped.length) continue;
61
+ const revisions = [...new Set(grouped.map(copy => copy.revision))];
62
+ clauses.push([id, revisions.length === 1 && /^[a-f0-9]{40}$/.test(revisions[0] ?? ''), `Copy ${scope} of operator-auth from one pinned Base revision; the seed records ${revisions.join(', ') || 'none'}.`]);
63
+ }
64
+ for (const copy of copies) {
65
+ for (const [path, expected] of Object.entries(copy.files)) {
66
+ let actual = null; try { actual = sha256(await readFile(await contained(root, path))); } catch {}
67
+ clauses.push([`SC-05.auth-copy.${path}`, actual === expected, actual === null
68
+ ? `${path} is missing; restore it from Base ${copy.revision}.`
69
+ : `${path} differs from Base ${copy.revision}; restore the copied bytes or record a new pin with refresh-base.`]);
70
+ }
71
+ if (copy.directory && copy.path) {
72
+ for (const path of await walk(root, copy.path)) {
73
+ if (!Object.hasOwn(copy.files, path) && !path.split('/').some(part => ['tests', 'node_modules', '.DS_Store'].includes(part))) {
74
+ clauses.push([`SC-05.auth-copy.${path}`, false, `${path} is not part of Base ${copy.revision}'s operator-auth; remove it or keep seed code outside the copied layers.`]);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return clauses;
80
+ }
@@ -0,0 +1,70 @@
1
+ import {mkdtemp,rm} from 'node:fs/promises';
2
+ import {join} from 'node:path';
3
+ import {tmpdir} from 'node:os';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {shared} from './shared.mjs';
6
+ import {RerError} from './errors.mjs';
7
+
8
+ // Admit the complete bounded workflow before it can acquire checkout custody.
9
+ // The child re-enters the same dispatcher with validated live queue membership.
10
+ // Hold the child's semantic outcome until queue and process custody are released.
11
+ // Child diagnostics remain captured/redacted on stderr. Long-lived servers never
12
+ // enter this boundary.
13
+ export async function boundedCommand(project,options,argv,out){
14
+ if(!['install','setup','build','verify','deploy','export','import'].includes(options.verb))return null;
15
+ const {commandPolicy,queuedCommand,queueLabel}=await shared('queue.mjs');
16
+ const policy=await commandPolicy();
17
+ const childArgs=[...argv];const cwdIndex=childArgs.indexOf('--cwd');if(cwdIndex>=0&&(childArgs.indexOf('--')<0||cwdIndex<childArgs.indexOf('--')))childArgs[cwdIndex+1]=project.root;
18
+ const directory=await mkdtemp(join(tmpdir(),'rer-bounded-'));
19
+ let store,result,finalEvent,errorEvent,buffer='';
20
+ const cancellation=new AbortController();
21
+ const handlers=new Map(['SIGINT','SIGTERM'].map(name=>[name,()=>cancellation.abort(name)]));
22
+ for(const [name,handler] of handlers)process.on(name,handler);
23
+ try{
24
+ const {createCommands}=await shared('commands.mjs');
25
+ store=await createCommands(directory,{logs:[]},{terminal:()=>{}});
26
+ result=await queuedCommand(process.execPath,[fileURLToPath(new URL('../bin/rer.mjs',import.meta.url)),...childArgs],{
27
+ cwd:project.root,signal:cancellation.signal,stdio:[!options.json&&!options.nonInteractive?'inherit':'ignore','pipe','pipe'],originalCI:policy.ci,env:{...process.env,...policy.environment,RER_BOUNDED_OUTPUT:'1'},
28
+ label:await queueLabel(project.root,options.verb,'workflow'),report:text=>out.progress(store.redact(text)+'\n'),onWaiting:options.json?details=>out.event('waiting',details):undefined,
29
+ spawn:(command,args,native)=>{
30
+ const child=store.spawn('Running bounded workflow',command,args,native);
31
+ const stderr=store.redactionStream(text=>out.progress(text));
32
+ child.stderr.on('data',chunk=>stderr.write(chunk));child.stderr.once('end',()=>stderr.end());
33
+ child.stdout.on('data',chunk=>{buffer+=chunk;let end;while((end=buffer.indexOf('\n'))>=0){const line=buffer.slice(0,end);buffer=buffer.slice(end+1);
34
+ let event;try{event=JSON.parse(line);}catch{}
35
+ if(event?.version===1&&event.verb===options.verb){
36
+ if(event.event==='result')finalEvent=event;
37
+ else if(event.event==='error')errorEvent=event;
38
+ else out.human(store.redact(JSON.stringify(event)));
39
+ }else if(!options.json)out.human(store.redact(line));
40
+ }});
41
+ child.stdout.once('end',()=>{if(buffer&&!options.json)out.human(store.redact(buffer));buffer='';});return child;
42
+ },
43
+ cleanup:async child=>{
44
+ await store.stop(child);
45
+ if(project.profile==='first-party'){
46
+ const {recoverInterrupted}=await shared('local/recovery.mjs');
47
+ const held=await recoverInterrupted(project.root);await held?.release();
48
+ }
49
+ }
50
+ });
51
+ }finally{
52
+ try{await store?.closeCommands();await policy.close();await rm(directory,{recursive:true,force:true});}
53
+ finally{for(const [name,handler] of handlers)process.off(name,handler);}
54
+ }
55
+ const termination=cancellation.signal.aborted?cancellation.signal.reason:undefined;
56
+ if(!result&&!termination)return null;
57
+ const exitCode=termination?(termination==='SIGINT'?130:143):result.code;
58
+ if(termination){
59
+ out.error(new RerError('cancelled',`Stopped by ${termination} after owned children were stopped.`,exitCode));
60
+ out.result(false,exitCode);
61
+ }else if(finalEvent&&finalEvent.exitCode===exitCode){
62
+ if(errorEvent)out.error(new RerError(errorEvent.code,store.redact(errorEvent.message),exitCode));
63
+ const {version,verb,event,ok,exitCode:childExit,...details}=finalEvent;
64
+ out.result(exitCode===0,exitCode,JSON.parse(store.redact(JSON.stringify(details))));
65
+ }else{
66
+ const error=new RerError('command_failed',result.supervisorEnded?store.redact(result.error):'Bounded command ended before its result; owned children and private files were recovered.',exitCode||1);
67
+ out.error(error);out.result(false,error.exitCode);return {exitCode:error.exitCode};
68
+ }
69
+ return {exitCode};
70
+ }
@@ -0,0 +1,63 @@
1
+ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import { nodeCache } from './node-runtime-cache.mjs';
5
+ import { projectNodePin } from './node-runtime-discovery.mjs';
6
+
7
+ // Read-only warnings about disk this machine spends twice: Node releases and
8
+ // workerd binaries that checkouts pin differently, quarantine copies nothing
9
+ // cleaned up, and a checkout on a volume where pnpm cannot clone from its store.
10
+ // None of these stop anything, so they never change doctor's ok.
11
+ const MB = 1024 * 1024;
12
+
13
+ async function size(path) {
14
+ const info = await stat(path).catch(() => undefined);
15
+ if (!info) return 0;
16
+ if (!info.isDirectory()) return info.size;
17
+ let total = 0;
18
+ for (const entry of await readdir(path).catch(() => [])) total += await size(join(path, entry));
19
+ return total;
20
+ }
21
+
22
+ // Other checkouts this machine knows about: the ones with a dev or preview
23
+ // server registered right now.
24
+ async function runningCheckouts(env) {
25
+ const directory = resolve(env.RER_SERVERS_DIR ?? join(homedir(), '.rer/servers'));
26
+ let records;
27
+ try { records = JSON.parse(await readFile(join(directory, 'state.json'), 'utf8')).running ?? []; } catch { return []; }
28
+ const found = [];
29
+ for (const checkout of new Set(records.map(record => record.checkout))) {
30
+ try { found.push(await realpath(checkout)); } catch {}
31
+ }
32
+ return found;
33
+ }
34
+
35
+ async function pins(root) {
36
+ const read = async file => { try { return JSON.parse(await readFile(join(root, file), 'utf8')); } catch { return {}; } };
37
+ const store = await readdir(join(root, 'node_modules/.pnpm')).catch(() => []);
38
+ return {
39
+ Node: await projectNodePin(root).catch(() => undefined),
40
+ pnpm: (await read('package.json')).packageManager?.replace(/^pnpm@/, ''),
41
+ workerd: [...new Set(store.filter(name => /^workerd@\d/.test(name)).map(name => name.slice('workerd@'.length).split('_')[0]))].sort().join(' and ') || undefined,
42
+ };
43
+ }
44
+
45
+ const COSTS = { Node: 'about 125 MB for each extra release', pnpm: 'a separate pnpm download', workerd: 'about 125-145 MB for each extra version' };
46
+
47
+ export async function cacheWarnings(root, { env = process.env } = {}) {
48
+ const warnings = [], here = await realpath(root), mine = await pins(here);
49
+ const cache = nodeCache(), entries = (await readdir(cache).catch(() => [])).filter(name => /^node-v\d/.test(name));
50
+ for (const name of entries.filter(name => name.includes('.quarantine-'))) {
51
+ warnings.push(`${join(cache, name)} is a leftover quarantine copy of a Node release (${Math.round(await size(join(cache, name)) / MB)} MB). Delete it with: rm -r ${JSON.stringify(join(cache, name))}`);
52
+ }
53
+ const releases = entries.filter(name => !name.includes('.quarantine-')).map(name => name.slice(6).split('-')[0]);
54
+ if (releases.length > 1 && mine.Node) warnings.push(`The Node cache ${cache} holds ${releases.length} releases (${releases.join(', ')}) and this checkout pins ${mine.Node}. Each release is about 125 MB; delete the ones no checkout pins.`);
55
+ const homeDevice = (await stat(homedir())).dev, peers = (await runningCheckouts(env)).filter(path => path !== here);
56
+ if ((await stat(here)).dev !== homeDevice) warnings.push(`This checkout is on another volume than your home folder, where rer keeps its Node cache and pnpm its package store. pnpm cannot clone packages across volumes, so this checkout copies them in full (0.4-0.9 GB instead of 0.02-0.17 GB). Move it to the same volume.`);
57
+ for (const peer of peers) {
58
+ const theirs = await pins(peer);
59
+ for (const [name, value] of Object.entries(mine)) if (value && theirs[name] && value !== theirs[name]) warnings.push(`${peer} pins ${name} ${theirs[name]} and this checkout pins ${value}; every distinct pin keeps its own copy (${COSTS[name]}). Align them when you can.`);
60
+ if ((await stat(peer)).dev !== (await stat(here)).dev) warnings.push(`${peer} is on another volume than this checkout, so the two cannot share pnpm's package store by cloning files.`);
61
+ }
62
+ return warnings;
63
+ }
@@ -0,0 +1,35 @@
1
+ import { writeFile, mkdir, mkdtemp, link, rm } from 'node:fs/promises';
2
+ import { dirname, basename, join } from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { fileRecords } from '../modules/portability/files.mjs';
7
+ const execute=promisify(execFile);
8
+ export async function containerTransfer({root,path,journal,store,signal,direction,binding,buckets,policy,transient}) {
9
+ if(direction==='import')for(const file of [path,journal].filter(Boolean))for await(const record of fileRecords(file)){}
10
+ const spec={direction,binding,buckets,policy,transient,file:basename(path),journal:journal?'/private/later.rer':undefined};
11
+ const encoded=JSON.stringify(spec,(_key,value)=>{if(typeof value==='function')throw new Error('Container local transfer needs a serializable archive policy; use native remote transport for this product');return value;});
12
+ const configuration=join(root,'.rer/container/transfer.json');await mkdir(dirname(configuration),{recursive:true,mode:0o700});await mkdir(dirname(path),{recursive:true,mode:0o700});await writeFile(configuration,encoded,{mode:0o600});
13
+ const identity='rer-local-'+createHash('sha256').update(root).digest('hex').slice(0,16),image=identity+':candidate',name='rer-transfer-'+randomUUID();
14
+ let child,temporary;
15
+ try {
16
+ await execute('docker',['create','--name',name,'--entrypoint','sh','-v',identity+'-data:/data',image,'-c','umask 077; exec flock -n --no-fork /data/host.lock node /runtime/modules/container/transfer.mjs']);
17
+ await execute('docker',['cp',configuration,name+':/private/transfer.json']);
18
+ if(direction==='import'){
19
+ await execute('docker',['cp',path,name+':/archive/'+spec.file]);
20
+ if(journal)await execute('docker',['cp',journal,name+':/private/later.rer']);
21
+ }
22
+ child=store.spawn('Transferring container archive','docker',['start','--attach',name],{cwd:root,signal});
23
+ let output='';child.stdout.on('data',chunk=>output+=chunk);const code=await new Promise(resolve=>{child.once('error',()=>resolve(1));child.once('close',resolve);});await store.stop(child);
24
+ if(code!==0)throw new Error('Container archive transfer failed');const receipt=JSON.parse(output.trim().split('\n').at(-1));
25
+ if(direction==='export'){
26
+ temporary=await mkdtemp(join(dirname(path),'.rer-transfer-'));
27
+ const copied=join(temporary,'archive.rer');await execute('docker',['cp',name+':/archive/'+spec.file,copied]);
28
+ for await(const record of fileRecords(copied)){}
29
+ await link(copied,path);
30
+ }
31
+ return{target:'local',platform:'container',...receipt};
32
+ }finally{
33
+ await store.stop(child).catch(()=>{});await execute('docker',['stop','-t','10',name]).catch(()=>{});await execute('docker',['rm','-f',name]).catch(()=>{});if(temporary)await rm(temporary,{recursive:true,force:true});
34
+ }
35
+ }
@@ -0,0 +1,62 @@
1
+ import { spawn, execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
4
+ import { join, resolve } from 'node:path';
5
+ import { createHash } from 'node:crypto';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { shared } from './shared.mjs';
8
+ import { selectedOrigin } from './local.mjs';
9
+ import { refuse, missing } from './errors.mjs';
10
+ import { refuseLocalModels } from '../modules/deploy-kit/local-only.mjs';
11
+ import { deployInputs } from '../modules/container/deploy-input.mjs';
12
+ import { ingressBindings } from '../modules/container/ssh-plan.mjs';
13
+ import { deploySSH, sshTarget } from '../modules/container/ssh.mjs';
14
+ const execute=promisify(execFile);
15
+ async function run(command,args,options={}) {
16
+ const child=spawn(command,args,{...options,stdio:'inherit'}),abort=()=>child.kill('SIGTERM');
17
+ process.once('SIGTERM',abort);process.once('SIGINT',abort);
18
+ try{await new Promise((resolve,reject)=>{child.once('error',reject);child.once('exit',code=>code===0?resolve():reject(new Error('Container command failed')));});}
19
+ finally{process.off('SIGTERM',abort);process.off('SIGINT',abort);if(child.exitCode===null&&child.signalCode===null)child.kill('SIGTERM');}
20
+ }
21
+ export async function containerCommand(project,options,out) {
22
+ if(options.stop)return runContainer(project,options,out);
23
+ const {localOperation}=await shared('local/operation.mjs'),{recoverInterrupted}=await shared('local/recovery.mjs');
24
+ const operation=await localOperation(project.root,process.env.RER_LOCAL_OPERATION);
25
+ try{await recoverInterrupted(project.root,operation);return await runContainer(project,options,out,operation);}catch(error){if(error.code==='SSH_HOST_REFUSAL')throw refuse(error.message);throw error;}finally{await operation.release();}
26
+ }
27
+ async function runContainer(project,options,out,operation) {
28
+ const {root}=project;
29
+ if(options.verb==='deploy'){try{await refuseLocalModels(root);}catch{throw refuse('Deploy refuses the local-only key file .rer/local/models.json. Remove live-dev credentials before deployment.');}}
30
+ const config=JSON.parse(await readFile(join(root,'deploy.json'),'utf8'));
31
+ if(!(config.platforms??['workers']).includes('container'))throw refuse('This seed does not declare container support.');
32
+ const name='rer-local-'+createHash('sha256').update(root).digest('hex').slice(0,16),image=name+':candidate';
33
+ if(options.stop){await execute('docker',['stop','-t','10',name]);return{stopped:true};}
34
+ if(['build','deploy'].includes(options.verb)){
35
+ if(options.verb==='deploy'){if(!options.host)throw missing('Container deploy requires --host ssh://user@host.');sshTarget(options.host);await deployInputs({root,envFile:options.envFile,localOnlyKeys:project.adapter?.localOnlyKeys});}
36
+ const ingress={...(options.bindAddress?{address:options.bindAddress}:{}),...(options.httpPort?{httpPort:options.httpPort}:{}),...(options.httpsPort?{httpsPort:options.httpsPort}:{})};
37
+ ingressBindings({...config.container?.ingress,...ingress});
38
+ if(!options.image){
39
+ const {withoutLocalVariables}=await shared('local/build.mjs'),{buildEnvironment}=await shared('local/environment.mjs');
40
+ const declaration=await readFile(join(root,'seed.json'),'utf8').then(JSON.parse).catch(()=>({}));
41
+ const declared=Array.isArray(declaration.env)?declaration.env.map(item=>item.name):Object.keys(declaration.env??{});
42
+ await withoutLocalVariables(root,async()=>{
43
+ const builder=fileURLToPath(new URL('../modules/container/build.mjs',import.meta.url));
44
+ await run(process.execPath,[builder,root],{cwd:root,env:buildEnvironment([...declared,...(config.secrets??[]).flatMap(item=>[item.binding,item.environment])])});
45
+ await run('docker',['build','--tag',image,join(root,'.rer/container/image')],{cwd:root});
46
+ },{nested:operation.token,settings:[...(project.adapter?.settings??[]),...(project.privateInputs??[])]});
47
+ }
48
+ if(options.verb==='build')return{platform:'container',image};
49
+ if(!options.host)throw missing('Container deploy requires --host ssh://user@host.');
50
+ return deploySSH({root,image:options.image??image,ingress,host:options.host,envFile:options.envFile,environment:options.environment,localOnlyKeys:project.adapter?.localOnlyKeys,dryRun:options.dryRun});
51
+ }
52
+ if(['dev','preview'].includes(options.verb)){
53
+ const selected=await selectedOrigin(project,options),env=join(root,'.rer/container/local.env');
54
+ await mkdir(join(root,'.rer/container'),{recursive:true,mode:0o700});
55
+ const local={...selected.vars,[config.originVar]:selected.url,PORT:'8080',RER_CONTAINER_IDLE_SECONDS:String(options.idleTimeout)};const {stdout}=await execute('docker',['image','inspect','--format','{{.Id}}',image]);local.RER_IMAGE_DIGEST=stdout.trim();
56
+ await writeFile(env,Object.entries(local).map(([key,value])=>`${key}=${String(value).replaceAll('\n','\\n')}`).join('\n')+'\n',{mode:0o600});
57
+ out.human('RER_OPEN '+selected.url);
58
+ await run('docker',['run','--rm','--name',name,'--env-file',env,'-p',`${selected.host}:${selected.port}:8080`,'-v',name+'-data:/data',image],{cwd:root});
59
+ return{platform:'container',stopped:true};
60
+ }
61
+ throw refuse('Unsupported container command');
62
+ }
@@ -0,0 +1,25 @@
1
+ export const pins = Object.freeze({ node: '22.20.0', pnpm: '10.12.4' });
2
+ // What a server start applies when the person sets nothing (RER_SERVER_LIMIT,
3
+ // RER_SERVER_IDLE_SECONDS or --idle-timeout). describe reports them.
4
+ export const serverDefaults = Object.freeze({ serverLimit: 10, idleSeconds: 43200 });
5
+ export const commonFlags = ['--help', '--cwd', '--non-interactive', '--json'];
6
+ const definitions = {
7
+ install: [[], 'Frozen dependency installation; no setup or global tool changes.'],
8
+ setup: [['--owner-email', '--owner-name', '--port', '--host', '--answers'], 'Build, migrate, prepare private settings and first owner; stop the temporary app.'],
9
+ dev: [['--port', '--host', '--idle-timeout', '--stop', '--as', '--login', '--models', '--models-service'], 'Foreground development with assets, HMR and the declared scheduled triggers run locally on their schedule. With devIdentity it opens signed in as the owner (--as PERSONA picks another start, --login uses the outbox sign-in instead); otherwise ordinary owner sign-in through the outbox.'],
10
+ preview: [['--port', '--host', '--idle-timeout', '--stop'], 'Foreground fixed production output; reuse intact build, preserve owner/state, idle timeout and explicit stop.'],
11
+ build: [[], 'Production build with private local settings excluded and restored.'],
12
+ verify: [[], 'Run the complete internal verification target.'],
13
+ reset: [['--yes'], 'Confirmed removal of the declared local inventory only.'],
14
+ deploy: [['--environment', '--dry-run', '--host', '--env-file', '--image', '--bind-address', '--http-port', '--https-port'], 'Owner-account deployment and health; dry run performs local validation/build only.'],
15
+ export: [['--target', '--output', '--origin', '--session-file'], 'Complete supported installation archive with integrity and exclusions.'],
16
+ import: [['--target', '--input', '--journal', '--origin', '--session-file', '--yes', '--legacy-v1', '--acknowledge-unverified-metadata', '--output'], 'Confirmed atomic restore into an empty destination; --journal LATER_ARCHIVE also fences rows that installation erased since. Backup-only local v1 recovery requires --legacy-v1 --acknowledge-unverified-metadata and a new --output v2 archive; --journal requires normal v2 import.'],
17
+ outbox: [['--port', '--host', '--owner-email', '--open'], 'Credential-free local outbox URL; optional ordinary sign-in request and explicit browser opening.'],
18
+ doctor: [[], 'Read-only installed tool, configuration and state diagnostics.'],
19
+ describe: [[], 'Read-only description of actual command capabilities.'],
20
+ check: [['--profile'], 'Read-only structural command contract and operations validation.'],
21
+ };
22
+ export const verbs = Object.fromEntries(Object.entries(definitions).map(([name, [flags, effects]]) => [name, { flags: [...commonFlags, ...flags, ...(['build','deploy','dev','preview','export','import'].includes(name)?['--platform']:[]), ...(['install','setup','dev','preview','build','verify','deploy','export','import'].includes(name)?['--no-queue']:[])], effects }]));
23
+ export const aliases = Object.fromEntries([...Object.keys(verbs).filter(v => v !== 'install'), 'release', 'deps', 'rer'].map(name => [name, name === 'deps' ? 'install' : name === 'rer' ? null : name]));
24
+ export const releaseFlags = ['--repo', '--project', '--repository', '--output', '--ref', '--dry-run', '--tag', '--clean-history', '--onto', '--env-file', '--no-queue'];
25
+ export const requiredHooks = ['preview', 'dependencies', 'build', 'migrate', 'start', 'health', 'owner', 'signIn', 'verify', 'deploy', 'portability', 'state', 'settings', 'input'];
@@ -0,0 +1,45 @@
1
+ import {linkApproval,approvalPath} from './dependency-approval.mjs';
2
+ import { unlink } from 'node:fs/promises';
3
+ import { missing } from './errors.mjs';
4
+ import { providerMetadata } from '../modules/dependencies/protocol.mjs';
5
+ import { readJson } from '../modules/operator-auth/identity/oidc/network.mjs';
6
+
7
+ // Only an owner-only, untracked token file is accepted. The token never enters
8
+ // setup answers, generated runtime configuration, logs or an archive.
9
+ export async function linkDependencies({project,input,origin,store,signal,appSetupToken,fetch:fetcher=globalThis.fetch,clock=Date.now}) {
10
+ const links=[];
11
+ for(const dependency of project.setupManifest?.dependencies??[]) {
12
+ const issuer=input?.[dependency.address];
13
+ if(!issuer) {if(!dependency.optional)throw missing('A required dependency address is missing.');continue;}
14
+ const tokenPath=approvalPath(project,dependency.interface);
15
+ if(!tokenPath) {
16
+ const current=await readJson(fetcher,origin+'/auth/dependencies/status',{timeoutMs:5000}).catch(()=>null);
17
+ if(current?.links?.some(link=>link.interface===dependency.interface && link.issuer===issuer && link.linked===true))continue;
18
+ }
19
+ if(!tokenPath)throw missing('Provider approval is required. Set RER_LINK_TOKEN_FILE to an owner-only, untracked link-token JSON file for this exact origin and callback set.');
20
+ const {path,value}=await linkApproval({project,dependency,issuer,origin,store,clock});
21
+ links.push({dependency,issuer,path,value});
22
+ }
23
+ // Open every initial app approval before the first provider can claim its owner.
24
+ // Hosted runtimes accept their private setup token; no local CLI HMAC is sent.
25
+ if(links.length && appSetupToken) {
26
+ store.addSecrets?.([appSetupToken]);
27
+ for(const {dependency,issuer} of links) {
28
+ try {
29
+ const response=await fetcher(origin+'/auth/dependencies/setup',{method:'POST',redirect:'manual',signal:signal?AbortSignal.any([signal,AbortSignal.timeout(5000)]):AbortSignal.timeout(5000),headers:{Origin:origin,'Content-Type':'application/json'},body:JSON.stringify({issuer,interface:dependency.interface,token:appSetupToken})});
30
+ // A claimed app still needs its owner's existing fresh email approval.
31
+ const accepted=!response.redirected && (response.status===200 || response.status===409);
32
+ await response.body?.cancel();if(!accepted)throw Error();
33
+ }catch{throw missing('Application setup approval was refused. Approve the link with fresh app-owner email proof before retrying.');}
34
+ }
35
+ }
36
+ for(const {dependency,issuer,path,value} of links) {
37
+ const metadata=await providerMetadata(issuer,fetcher,dependency.interface);
38
+ let result;try {result=await readJson(fetcher,metadata.link.initiate,{timeoutMs:10000},{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({origin,interface:dependency.interface,token:value.token})});}catch{throw missing('Provider link approval was refused or the provider is unavailable.');}
39
+ if(result.linked!==true)throw missing('Provider approval did not complete linking.');
40
+ const status=await readJson(fetcher,origin+'/auth/dependencies/status',{timeoutMs:5000});
41
+ if(!status.links?.some(link=>link.interface===dependency.interface && link.issuer===issuer && link.linked===true))throw missing('The application has not accepted the provider link.');
42
+ await unlink(path);
43
+ store.report('Dependency linked; its one-time approval token was deleted.');
44
+ }
45
+ }
@@ -0,0 +1,33 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import {execFileSync} from 'node:child_process';
3
+ import {relative,resolve} from 'node:path';
4
+ import {contained} from './project.mjs';
5
+ import {invalid,missing} from './errors.mjs';
6
+ import {CALLBACK} from '../modules/dependencies/protocol.mjs';
7
+ export function approvalPath(project,contract) {
8
+ const single=process.env.RER_LINK_TOKEN_FILE,mapping=process.env.RER_LINK_TOKEN_FILES;
9
+ if(single && mapping)throw invalid('Choose one link approval input form.');
10
+ if(!mapping)return single;
11
+ let files;try{files=JSON.parse(mapping);}catch{throw invalid('RER_LINK_TOKEN_FILES must be a JSON map of interfaces to private file paths.');}
12
+ const declared=new Set((project.setupManifest?.dependencies??[]).map(d=>d.interface));
13
+ if(!files || typeof files!=='object' || Array.isArray(files) || Object.entries(files).some(([key,value])=>!declared.has(key)||typeof value!=='string'||!value))throw invalid('Link approval files must name declared interfaces and private file paths.');
14
+ const paths=Object.values(files).map(path=>resolve(project.root,path));
15
+ if(new Set(paths).size!==paths.length)throw invalid('Each interface requires a distinct approval file.');
16
+ return files[contract];
17
+ }
18
+ export async function linkApproval({project,dependency,issuer,origin,store,clock=Date.now}) {
19
+ const tokenPath=approvalPath(project,dependency.interface);
20
+ if(!tokenPath)throw missing('Provider approval is required. Supply RER_LINK_TOKEN_FILE.');
21
+ const path=await contained(project.root,tokenPath,{privateFile:true});
22
+ let tracked;
23
+ try{tracked=execFileSync('git',['ls-files','--error-unmatch','--',relative(project.root,path)],{cwd:project.root,stdio:['ignore','pipe','ignore']}).length>0;}catch(e){if(e.status!==1)throw invalid('Link token provenance could not be verified in this checkout.');}
24
+ if(tracked)throw invalid('A tracked link token is refused. Create a fresh token in an untracked private file.');
25
+ let value;try{value=JSON.parse(await readFile(path,'utf8'));}catch{throw invalid('Link-token file is invalid.');}
26
+ if(typeof value.token==='string')store?.addSecrets?.([value.token]);
27
+ const callbacks=[origin+CALLBACK,...dependency.paths.map(p=>origin+p)].sort();
28
+ if(typeof value.token!=='string' || !value.token || value.origin!==origin || value.issuer!==issuer || value.interface!==dependency.interface ||
29
+ !Array.isArray(value.callbacks) || JSON.stringify([...value.callbacks].sort())!==JSON.stringify(callbacks) ||
30
+ !Number.isSafeInteger(value.issuedAt) || !Number.isSafeInteger(value.expiresAt) || value.issuedAt>clock() || value.expiresAt<=clock() || value.expiresAt-value.issuedAt>3600000)
31
+ throw missing('Link approval must name this exact origin, interface and callback set, be unused, and expire within one hour.');
32
+ return {path,value};
33
+ }
@@ -0,0 +1,34 @@
1
+ import {providerMetadata} from '../modules/dependencies/protocol.mjs';
2
+ import {readdir,lstat} from 'node:fs/promises';
3
+ import {join} from 'node:path';
4
+ // Canonical local D1, read-only. Diagnostics contain no key, token or metadata body.
5
+ export async function localDependencyLinks(root) {
6
+ let directory=root;
7
+ for(const part of ['.wrangler','state','v3','d1','miniflare-D1DatabaseObject']) {
8
+ directory=join(directory,part);const info=await lstat(directory).catch(()=>null);
9
+ if(!info)return [];if(info.isSymbolicLink() || !info.isDirectory())return [{ok:false,status:'unsafe_state'}];
10
+ }
11
+ const {DatabaseSync}=await import('node:sqlite');
12
+ const found=[];
13
+ for(const file of await readdir(directory,{withFileTypes:true})) {
14
+ if(!file.isFile() || !file.name.endsWith('.sqlite'))continue;
15
+ const db=new DatabaseSync(join(directory,file.name),{readOnly:true});
16
+ try {
17
+ if(!db.prepare("SELECT 1 FROM sqlite_master WHERE name='rer_dependency_link'").get())continue;
18
+ for(const row of db.prepare('SELECT interface,issuer,needs_relink FROM rer_dependency_link').all()) {
19
+ found.push(row);
20
+ }
21
+ }finally{db.close();}
22
+ }
23
+ return found;
24
+ }
25
+
26
+ export async function dependencyDoctor(root,{fetch:fetcher=globalThis.fetch}={}) {
27
+ const links=await localDependencyLinks(root),result=[];
28
+ for(const row of links) {
29
+ let status=row.needs_relink?'needs_relink':row.status??'linked';
30
+ if(status==='linked')try{await providerMetadata(row.issuer,fetcher,row.interface);}catch{status='dangling_provider';}
31
+ result.push({interface:row.interface,ok:status==='linked',status});
32
+ }
33
+ return result;
34
+ }
@@ -0,0 +1,43 @@
1
+ import {linkApproval,approvalPath} from './dependency-approval.mjs';
2
+ import { localDependencyLinks } from './dependency-doctor.mjs';
3
+ import { readJson } from '../modules/operator-auth/identity/oidc/network.mjs';
4
+ import { originOf } from '../modules/dependencies/protocol.mjs';
5
+ import { invalid, missing } from './errors.mjs';
6
+ import { shared } from './shared.mjs';
7
+
8
+ // Read the selected graph before setup writes anything. Optional selected edges
9
+ // participate just like required edges; disconnected declarations do not.
10
+ export async function dependencyInputs({project,input,origin,fetch:fetcher=globalThis.fetch}) {
11
+ const declarations=project.setupManifest?.dependencies??[],selected=declarations.filter(d=>input[d.address]);
12
+ if(declarations.some(d=>!d.optional&&!input[d.address]))throw missing('A required dependency address is missing.');
13
+ if(!selected.length)return;
14
+ const existing=await localDependencyLinks(project.root);
15
+ const pending=selected.filter(d=>approvalPath(project,d.interface) || !existing.some(link=>link.interface===d.interface && link.issuer===input[d.address] && !link.needs_relink));
16
+ if(!pending.length)return;
17
+ const nodes=new Map([[origin,[...new Set(selected.map(d=>input[d.address]))]]]),documents=new Map();
18
+ const queue=selected.map(d=>({origin:input[d.address],interface:d.interface}));
19
+ while(queue.length) {
20
+ const item=queue.shift();
21
+ try{originOf(item.origin);}catch{throw invalid('A selected dependency must be an exact HTTPS origin.');}
22
+ if(item.origin===origin)throw invalid('Selected dependencies contain a cycle.');
23
+ let doc=documents.get(item.origin);
24
+ if(!doc) {
25
+ if(nodes.size>100)throw invalid('The selected dependency graph is too large.');
26
+ try{doc=await readJson(fetcher,item.origin+'/.well-known/rer-dependencies',{timeoutMs:5000});}catch{throw missing('A selected provider dependency document is unavailable.');}
27
+ if(doc.origin!==item.origin||doc.version!==1||!Array.isArray(doc.provides)||new Set(doc.provides).size!==doc.provides.length||doc.provides.some(i=>typeof i!=='string'||!/^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(i))||!Array.isArray(doc.dependencies))throw invalid('A selected provider dependency document is invalid.');
28
+ documents.set(item.origin,doc);
29
+ const edges=[],interfaces=new Set();
30
+ for(const edge of doc.dependencies) {
31
+ if(!edge || typeof edge.interface!=='string'||!/^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(edge.interface)||interfaces.has(edge.interface)||typeof edge.optional!=='boolean'||edge.cardinality!==1)throw invalid('A selected provider dependency declaration is invalid.');
32
+ interfaces.add(edge.interface);
33
+ if(!edge.provider){if(!edge.optional)throw missing('A selected provider has an unlinked required dependency.');continue;}
34
+ edges.push(edge.provider);queue.push({origin:edge.provider,interface:edge.interface});
35
+ }
36
+ nodes.set(item.origin,[...new Set(edges)]);
37
+ }
38
+ if(!doc.provides.includes(item.interface))throw invalid('The selected provider does not provide the declared interface.');
39
+ }
40
+ const {validateDependencyGraph}=await shared('schema/dependencies.mjs');
41
+ try{validateDependencyGraph([...nodes].map(([id,dependencies])=>({id,dependencies})));}catch{throw invalid('Selected dependencies contain a cycle or invalid reference.');}
42
+ for(const dependency of pending)await linkApproval({project,dependency,issuer:input[dependency.address],origin});
43
+ }
@@ -0,0 +1,118 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { contained } from './project.mjs';
3
+ import { parseJsonc } from './jsonc.mjs';
4
+
5
+ // Where a seed's real Worker configuration lives: the path devIdentity.deployConfig
6
+ // names, else wrangler.jsonc, wrangler.json and wrangler.toml, then a deploy
7
+ // template such as deploy/wrangler.template.toml. A file the adapter lists in
8
+ // generatedOutputs holds whichever configuration was built last, so it is never a
9
+ // source. `rer check` (the deployed main) and `rer dev`/`rer preview` (the Cron
10
+ // Triggers) read these same files.
11
+ const deployConfigs = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'], deployTemplates = ['deploy/wrangler.template.toml', 'deploy/wrangler.template.jsonc', 'deploy/wrangler.template.json', 'wrangler.template.toml', 'wrangler.template.jsonc', 'wrangler.template.json'];
12
+
13
+ export function deployConfigPaths(adapter) {
14
+ const declared = adapter?.devIdentity?.deployConfig, generated = new Set((adapter?.generatedOutputs ?? []).map(path => path.replace(/^\.\//, '')));
15
+ return typeof declared === 'string' ? [declared] : [...deployConfigs.filter(path => !generated.has(path)), ...deployTemplates];
16
+ }
17
+
18
+ export async function readProjectText(root, path) {
19
+ try { return await readFile(await contained(root, path), 'utf8'); } catch { return null; }
20
+ }
21
+
22
+ // Top-level `main = "..."` only: a table header ends the top level.
23
+ export function tomlMain(source) {
24
+ for (const line of source.split('\n')) {
25
+ if (/^\s*\[/.test(line)) return undefined;
26
+ const match = /^\s*main\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(line);
27
+ if (match) return match[1] ?? match[2];
28
+ }
29
+ }
30
+ export function jsoncMain(source) {
31
+ try { return JSON.parse(source.replace(/\/\*[\s\S]*?\*\/|^\s*\/\/.*$/gm, '')).main; } catch { return undefined; }
32
+ }
33
+
34
+ // The deployed Worker entry (`main`) from the first of those files that exists.
35
+ export async function deployedMain(root, adapter) {
36
+ for (const path of deployConfigPaths(adapter)) {
37
+ const source = await readProjectText(root, path);
38
+ if (source === null) continue;
39
+ return (/\.toml$/.test(path) ? tomlMain(source) : jsoncMain(source))?.replace(/^\.\//, '');
40
+ }
41
+ }
42
+
43
+ // Statements of a TOML file with comments removed: one line, or several while a
44
+ // value's bracket is still open. Strings are respected.
45
+ function tomlStatements(source) {
46
+ const statements = [];
47
+ let current = '', depth = 0, quote = '';
48
+ for (let i = 0; i < source.length; i++) {
49
+ const char = source[i];
50
+ if (quote) {
51
+ current += char;
52
+ if (char === '\\' && quote === '"') current += source[++i] ?? '';
53
+ else if (char === quote) quote = '';
54
+ } else if (char === '"' || char === "'") { quote = char; current += char; }
55
+ else if (char === '#') { while (i + 1 < source.length && source[i + 1] !== '\n') i++; }
56
+ else if (char === '\n') {
57
+ if (depth > 0) current += ' ';
58
+ else { statements.push(current); current = ''; }
59
+ } else {
60
+ if (current.includes('=')) { if (char === '[' || char === '{') depth++; else if (char === ']' || char === '}') depth--; }
61
+ current += char;
62
+ }
63
+ }
64
+ statements.push(current);
65
+ return statements.map(text => text.trim()).filter(Boolean);
66
+ }
67
+
68
+ function tomlStrings(text, path) {
69
+ const list = /^\[([\s\S]*)\]$/.exec(text.trim());
70
+ if (!list) throw new Error(`${path} triggers.crons must be a list of cron expressions.`);
71
+ const body = list[1], values = [], pattern = /\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)')\s*(?:,|$)/y;
72
+ while (pattern.lastIndex < body.length && body.slice(pattern.lastIndex).trim()) {
73
+ const match = pattern.exec(body);
74
+ if (!match) throw new Error(`${path} triggers.crons must be a list of cron expressions.`);
75
+ values.push(match[1] === undefined ? match[2] : JSON.parse(`"${match[1]}"`));
76
+ }
77
+ return values;
78
+ }
79
+
80
+ // The top-level `triggers.crons` of a wrangler.toml (the [triggers] table, a
81
+ // dotted `triggers.crons` key, or an inline `triggers = { crons = [...] }`);
82
+ // undefined when the file declares none. Environment tables are not read.
83
+ export function tomlCrons(source, path = 'wrangler.toml') {
84
+ let table = '';
85
+ for (const statement of tomlStatements(source)) {
86
+ const header = /^\[\[?\s*([^\]]+?)\s*\]\]?$/.exec(statement);
87
+ if (header) { table = header[1].replace(/\s+/g, ''); continue; }
88
+ const pair = /^([A-Za-z0-9_.\-"' ]+?)\s*=\s*([\s\S]*)$/.exec(statement);
89
+ if (!pair) continue;
90
+ const key = (table ? `${table}.` : '') + pair[1].replace(/\s+/g, '');
91
+ if (key === 'triggers.crons') return tomlStrings(pair[2], path);
92
+ if (key === 'triggers') {
93
+ const inline = /crons\s*=\s*(\[[\s\S]*?\])/.exec(pair[2]);
94
+ if (inline) return tomlStrings(inline[1], path);
95
+ }
96
+ }
97
+ }
98
+
99
+ function jsoncCrons(source, path) {
100
+ let config;
101
+ try { config = parseJsonc(source); } catch (error) { throw new Error(`${path}: ${error.message}`, { cause: error }); }
102
+ const crons = config.triggers?.crons;
103
+ if (crons === undefined) return undefined;
104
+ if (!Array.isArray(crons) || crons.some(cron => typeof cron !== 'string')) throw new Error(`${path} triggers.crons must be a list of cron expressions.`);
105
+ return crons;
106
+ }
107
+
108
+ // Every file of the discovery that declares triggers.crons, as { path, crons }.
109
+ export async function declaredCronSources(root, adapter) {
110
+ const found = [];
111
+ for (const path of deployConfigPaths(adapter)) {
112
+ let source;
113
+ try { source = await readFile(await contained(root, path, { optional: true }), 'utf8'); } catch (error) { if (error.code === 'ENOENT') continue; throw error; }
114
+ const crons = /\.toml$/.test(path) ? tomlCrons(source, path) : jsoncCrons(source, path);
115
+ if (crons !== undefined) found.push({ path, crons });
116
+ }
117
+ return found;
118
+ }