@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,42 @@
1
+ // Only this module can mark a failure as safe for the browser. Never render a
2
+ // caught error's message, code, cause, command, arguments, output or environment.
3
+ const approved = new WeakMap();
4
+ const tools = Object.freeze({node:'Node.js',npm:'npm',pnpm:'pnpm',git:'Git'});
5
+ const steps = Object.freeze({dependencies:'installing dependencies',setup:'setting up the application',build:'building the application',verify:'verifying the application',start:'starting the application',deploy:'deploying the application'});
6
+ export function installationFailure(code, details = {}) {
7
+ let message;
8
+ if(code==='node_version' && typeof details.version==='string' && /^\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(details.version) && !details.version.includes('\n')) {
9
+ message=`Install Node.js ${details.version}, then restart this installer with that version and resume your saved setup.`;
10
+ } else if(code==='missing_tool' && typeof details.tool==='string' && Object.hasOwn(tools,details.tool)) {
11
+ const action=details.tool==='npm'?'Install Node.js with npm included':`Install ${tools[details.tool]}`;
12
+ message=`${tools[details.tool]} was not found. ${action} and make it available on PATH, then restart this installer and resume your saved setup.`;
13
+ } else if(code==='command_failed' && typeof details.step==='string' && Object.hasOwn(steps,details.step)) {
14
+ message=`Installation stopped while ${steps[details.step]}. Ask your agent to check the edition's operating command for that step, correct the cause, then select Start again. Your setup answers are saved; you may need to supply credentials again.`;
15
+ } else {
16
+ throw new TypeError('Choose a supported installation failure and its documented details.');
17
+ }
18
+ // A captured command diagnostic from this module's command boundary keeps its
19
+ // first line (the headline the page shows) and its output and log path; the
20
+ // canned guidance follows that line. Any other value is ignored.
21
+ const captured=approved.get(details.diagnostic);
22
+ if(code==='command_failed'&&captured?.code==='captured_command_failed'){
23
+ const [first,...rest]=captured.message.split('\n');
24
+ message=[first,message,...rest].join('\n');
25
+ }
26
+ const error=new Error(message);
27
+ approved.set(error,{code,message,...(captured?.details ? {details:captured.details} : {})});
28
+ return error;
29
+ }
30
+ export function installationFailureMessage(error) {
31
+ return approved.get(error)?.message || 'Installation stopped unexpectedly. Review the last installation step shown here and ask your agent to inspect the edition’s installer and operating commands. After correcting the cause, select Start again. Your setup answers are saved; you may need to supply credentials again.';
32
+ }
33
+
34
+ // Only the command capture boundary calls this with its redacted diagnostic.
35
+ // Identity approval remains independent of mutable Error properties.
36
+ export function commandFailure(message, details = {}) {
37
+ const error=new Error(message);approved.set(error,{code:'captured_command_failed',message,details:{...details}});return error;
38
+ }
39
+
40
+ // Terminal callers may expose only diagnostics approved at the capture boundary.
41
+ // A prior diagnostic's metadata must not replace the current composed message.
42
+ export const approvedFailure = error => { const value=approved.get(error); return value ? {...value.details,...value} : undefined; };
@@ -0,0 +1,7 @@
1
+ // SETUP's shared validateSetupValue contract permits 16,384 UTF-16 units.
2
+ export const SETUP_VALUE_LIMIT = 16384;
3
+ // JSON can escape each unit as six ASCII bytes. Bound requests by declared
4
+ // fields, while accepting any valid combination, including secure envelopes.
5
+ export function setupInputLimit(model) {
6
+ return 1024 + Object.keys(model.fields).reduce((size,key)=>size+6*(key.length+SETUP_VALUE_LIMIT)+16,0);
7
+ }
@@ -0,0 +1,24 @@
1
+ // Installer mechanics are defined here once; application bindings belong in SETUP.
2
+ export const hostingKeys = ['target', 'accountId', 'prefix', 'appOrigin', 'apiToken'];
3
+ export function installerInputs({hosting=false, ownerBootstrap=false}={}) {
4
+ const fields={},groups=[],secretScopes={};
5
+ const nonempty=value=>typeof value==='string'&&Boolean(value.trim());
6
+ if(ownerBootstrap){
7
+ fields.displayName={label:'Your name',help:'Shown as the first owner of this installation.',valid:value=>nonempty(value)&&value.length<=120};
8
+ groups.push({id:'local-owner',title:'First owner',keys:['displayName']});
9
+ }
10
+ if(hosting){
11
+ Object.assign(fields,{
12
+ target:{label:'Where should the workspace run?',options:[['local','On this computer'],['remote','Online for your team']],valid:value=>['local','remote'].includes(value)},
13
+ accountId:{label:'Cloudflare account',optional:true,valid:value=>/^[a-f0-9]{32}$/i.test(value||'')},
14
+ prefix:{label:'Resource name',optional:true,valid:value=>/^[a-z][a-z0-9-]{2,39}$/.test(value||'')},
15
+ appOrigin:{label:'Workspace address',optional:true,type:'url',valid:value=>{try{const url=new URL(value);return url.protocol==='https:'&&!url.username&&!url.password&&!url.href.includes('?')&&!url.href.includes('#')&&url.origin===value;}catch{return false;}}},
16
+ apiToken:{label:'Cloudflare API token',optional:true,type:'password',valid:nonempty},
17
+ prefixMode:{optional:true,valid:value=>['automatic','custom'].includes(value)},
18
+ addressMode:{optional:true,valid:value=>['automatic','custom'].includes(value)},
19
+ });
20
+ secretScopes.apiToken=['accountId'];
21
+ groups.push({id:'hosting',title:'Hosting & connections',kind:'hosting',keys:hostingKeys});
22
+ }
23
+ return {fields,groups,secretScopes};
24
+ }
@@ -0,0 +1,34 @@
1
+ import { loadInstallerSeed, refuseLegacySetup } from './setup-manifest.mjs';
2
+ import { spawn } from 'node:child_process';
3
+ import { relative } from 'node:path';
4
+ import { readAnswers, readSecrets } from './prefill.mjs';
5
+ import { startInstaller } from './server.mjs';
6
+ export async function launch(root,seed){
7
+ refuseLegacySetup(seed);
8
+ if(process.argv.includes('--help')){console.log(`Launch the standalone ${seed.product} installer before installing app dependencies.\nCommand-line setup instead: rer install, then rer setup.\nUsage: node ${relative(process.cwd(),process.argv[1])} [--port <local-port>] [--no-open] [--agent] [--no-prefill | --resume] [--answers answers.json] [--secrets-stdin]`);process.exit(0)}
9
+ let noOpen=false,port=0,answersPath,secrets=false,noPrefill=false,resume=false,agent=false;
10
+ const args=process.argv.slice(2);
11
+ for(let index=0;index<args.length;index++){
12
+ if(args[index]==='--no-open')noOpen=true;
13
+ else if(args[index]==='--port'&&/^\d+$/.test(args[index+1]||''))port=Number(args[++index]);
14
+ else if(args[index]==='--answers'&&args[index+1])answersPath=args[++index];
15
+ else if(args[index]==='--secrets-stdin')secrets=true;
16
+ else if(args[index]==='--no-prefill')noPrefill=true;
17
+ else if(args[index]==='--agent')agent=true;
18
+ else if(args[index]==='--resume')resume=true;
19
+ else throw Error('Use --help for installer options.');
20
+ }
21
+ if(noPrefill&&(answersPath||secrets))throw Error('--no-prefill cannot be combined with supplied answers or secrets.');
22
+ if(noPrefill&&resume)throw Error('Choose --no-prefill for a new empty session or --resume for saved answers.');
23
+ seed=await loadInstallerSeed(root,seed);
24
+ const contract={...seed};delete contract.model;
25
+ const answers=answersPath?await readAnswers(answersPath,seed.model):undefined;
26
+ let secretInput=secrets?await readSecrets(process.stdin,seed.model):undefined;
27
+ const server=await startInstaller(root,{seed:contract,port,answers,secretInput,noPrefill,resume,agent});secretInput=undefined;
28
+ console.log(`${seed.product} installer: ${server.origin}\nKeep this terminal open while installing and using the local app.`);
29
+ for(const signal of ['SIGTERM','SIGINT','SIGHUP'])process.on(signal,async()=>{await server.close();process.exit()});
30
+
31
+ if(!noOpen){const tool=process.platform==='darwin'?'open':process.platform==='win32'?'explorer.exe':'xdg-open';const child=spawn(tool,[server.origin],{stdio:'ignore',shell:false});const fallback=()=>console.log('Open the installer URL above in your browser.');child.on('error',fallback);child.on('exit',code=>{if(code)fallback()});}
32
+
33
+ return server;
34
+ }
@@ -0,0 +1,64 @@
1
+ import { lstat, rename, link, unlink } from 'node:fs/promises'
2
+ import { isAbsolute, join } from 'node:path'
3
+ import { assertRegular, inspect } from './files.mjs'
4
+
5
+ // The encoded relative name is also the recovery record. Publishing it by
6
+ // rename records the destination atomically with moving the original inode.
7
+ export function hiddenBuildName(file) {
8
+ validateName(file)
9
+ return `original.${Buffer.from(file).toString('base64url')}.private`
10
+ }
11
+
12
+ function validateName(file) {
13
+ if (typeof file !== 'string' || !file || isAbsolute(file)
14
+ || file.split(/[\\/]/).some(part => !part || part === '..' || part === '.')) {
15
+ throw new Error('Private build files must be contained checkout paths.')
16
+ }
17
+ }
18
+
19
+ export function buildDestination(name) {
20
+ const match = /^original\.([A-Za-z0-9_-]+)\.private$/.exec(name)
21
+ if (!match) return
22
+ const file = Buffer.from(match[1], 'base64url').toString('utf8')
23
+ if (hiddenBuildName(file) !== name) throw new Error('Invalid private build recovery record.')
24
+ return file
25
+ }
26
+
27
+ export async function inspectBuildFile(root, file, { publication = false } = {}) {
28
+ validateName(file)
29
+ const parts = file.split(/[\\/]/)
30
+ let path = root
31
+ for (const [index, part] of parts.entries()) {
32
+ path = join(path, part)
33
+ const status = await inspect(path)
34
+ if (!status) return { path: join(root, ...parts) }
35
+ if (index < parts.length - 1) {
36
+ if (!status.isDirectory() || status.isSymbolicLink()) throw new Error('Unsafe private build file ancestor.')
37
+ } else {
38
+ if (publication && status.isFile() && !status.isSymbolicLink() && (!process.getuid || status.uid === process.getuid())) return { path, status }
39
+ assertRegular(status); return { path, status }
40
+ }
41
+ }
42
+ }
43
+
44
+ export async function hideBuildFile(item, work, file) {
45
+ const current = await lstat(item.path)
46
+ if (current.ino !== item.status.ino || current.dev !== item.status.dev) throw new Error('Private settings changed during build; run again.')
47
+ const hidden = join(work, hiddenBuildName(file))
48
+ await rename(item.path, hidden)
49
+ return { path: hidden, file }
50
+ }
51
+
52
+ export async function restoreBuildFile(root, item) {
53
+ const destination = await inspectBuildFile(root, item.file, { publication: true })
54
+ const hidden = await inspect(item.path)
55
+ if (!hidden) return
56
+ if (destination.status?.ino === hidden.ino && destination.status?.dev === hidden.dev) {
57
+ await unlink(item.path)
58
+ return
59
+ }
60
+ assertRegular(hidden)
61
+ // Linking keeps exact bytes/mode and refuses a concurrently created file.
62
+ await link(item.path, destination.path)
63
+ await unlink(item.path)
64
+ }
@@ -0,0 +1,49 @@
1
+ import { mkdtemp, readFile, rm } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { privateDirectory } from './files.mjs'
4
+ import { localVariables, secretValues } from './environment.mjs'
5
+ import { localOperation } from './operation.mjs'
6
+ import { recoverInterrupted } from './recovery.mjs'
7
+ import { inspectBuildFile, hideBuildFile, restoreBuildFile } from './build-files.mjs'
8
+
9
+ // Hide every declared private setting and backup under the shared operation
10
+ // lock. Renames and exclusive links preserve each original inode and mode;
11
+ // encoded names let the next operation recover a hard-killed build.
12
+ export async function withoutLocalVariables(root, action, { nested = process.env.RER_LOCAL_OPERATION, report, settings = ['.dev.vars', '.dev.vars.previous'] } = {}) {
13
+ const hidden = []
14
+ let work, operation, restoration
15
+ const restore = () => restoration ??= (async () => {
16
+ const failures = []
17
+ for (const item of hidden) {
18
+ try { await restoreBuildFile(root, item) } catch {
19
+ failures.push(`Could not restore ${item.file} without replacing a concurrent file. Original retained at ${item.path}.`)
20
+ }
21
+ }
22
+ if (work && !failures.length) await rm(work, { recursive: true })
23
+ await operation?.release()
24
+ if (failures.length) {
25
+ if (report) for (const message of failures) report(message)
26
+ else throw new Error(failures.join('\n'))
27
+ }
28
+
29
+ })()
30
+ try {
31
+ operation = await localOperation(root, nested)
32
+ if (!nested) await recoverInterrupted(root, operation)
33
+ const files = []
34
+ for (const file of new Set(['.dev.vars', '.dev.vars.previous', ...settings])) {
35
+ const item = await inspectBuildFile(root, file)
36
+ if (item.status) files.push({ ...item, file })
37
+ }
38
+ let secrets = []
39
+ const vars = files.find(item => item.file === '.dev.vars')
40
+ if (vars) secrets = secretValues(localVariables(await readFile(vars.path, 'utf8')))
41
+ if (files.length) {
42
+ const directory = join(root, '.wrangler/local-build')
43
+ await privateDirectory(root, directory)
44
+ work = await mkdtemp(join(directory, 'run-'))
45
+ for (const item of files) hidden.push(await hideBuildFile(item, work, item.file))
46
+ }
47
+ return await action({ secrets, operation, restore })
48
+ } finally { await restore() }
49
+ }
@@ -0,0 +1,30 @@
1
+ import { parseEnv } from 'node:util'
2
+
3
+ export function localVariables(text) {
4
+ // Node's dotenv parser can consume the next assignment after an unquoted
5
+ // whitespace-only value. Keep such values empty for the preflight checks.
6
+ return parseEnv(text.replace(/(^[\t ]*(?:export[\t ]+)?[A-Za-z_]\w*[\t ]*=)[\t ]+(?=\r?$)/gm, '$1'))
7
+ }
8
+
9
+ // Inherited application bindings never reach a build or migration command.
10
+ // The default names are the Base operator-auth and mail bindings.
11
+ export const applicationBindings = /^(SESSION_SECRET|SETUP_TOKEN|OWNER_EMAIL|AUTH_|MAIL_|OIDC_|AI_|MODELS_|RER_MODELS_)/
12
+
13
+ export function buildEnvironment(bindings = applicationBindings, inherited = process.env) {
14
+ const env = { ...inherited, CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV: 'false', CLOUDFLARE_INCLUDE_PROCESS_ENV: 'false', WRANGLER_SEND_METRICS: 'false' }
15
+ const declared = Array.isArray(bindings) ? new Set(bindings) : null
16
+ for (const key of Object.keys(env)) if (declared ? declared.has(key) || applicationBindings.test(key) : bindings.test(key)) delete env[key]
17
+ return env
18
+ }
19
+
20
+ // Values printed by a child are redacted when their name marks a credential.
21
+ export const secretValues = (vars) => Object.entries(vars).filter(([key]) => /SECRET|TOKEN|API_KEY/.test(key)).map(([, value]) => value).filter(Boolean)
22
+
23
+ export function localOrigin(vars) {
24
+ let url
25
+ try { url = new URL(vars.AUTH_ORIGIN) } catch { /* Use a fixed, non-secret diagnostic. */ }
26
+ if (vars.AUTH_LOCAL !== 'true' || !url || url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || url.origin !== vars.AUTH_ORIGIN || url.username || url.password) {
27
+ throw new Error('Invalid local origin in .dev.vars: AUTH_ORIGIN must be http://127.0.0.1:PORT with AUTH_LOCAL=true. Run rer setup to save a valid one. If you changed AUTH_ORIGIN yourself, put back the http://127.0.0.1:PORT origin setup saved, or pick a port with rer setup --port PORT.')
28
+ }
29
+ return url
30
+ }
@@ -0,0 +1,35 @@
1
+ import { lstat, mkdir, open } from 'node:fs/promises'
2
+ import { relative, resolve, sep } from 'node:path'
3
+
4
+ export async function inspect(path) {
5
+ try { return await lstat(path) } catch (error) {
6
+ if (error.code === 'ENOENT') return undefined
7
+ throw error
8
+ }
9
+ }
10
+
11
+ // Inspect every ancestor: mkdir({recursive:true}) alone follows symlinks.
12
+ export async function privateDirectory(root, path) {
13
+ const parts = relative(root, resolve(path)).split(sep)
14
+ if (parts.includes('..')) throw new Error('Unsafe local setup directory.')
15
+ let current = root
16
+ for (const part of parts) {
17
+ current = resolve(current, part)
18
+ const status = await inspect(current)
19
+ if (status && (!status.isDirectory() || status.isSymbolicLink())) {
20
+ throw new Error('Unsafe local setup directory; remove its symlink or non-directory first.')
21
+ }
22
+ if (!status) await mkdir(current, { mode: 0o700 })
23
+ }
24
+ }
25
+
26
+ export async function privateFile(path, content) {
27
+ const handle = await open(path, 'wx', 0o600)
28
+ try { await handle.writeFile(content) } finally { await handle.close() }
29
+ }
30
+
31
+ export function assertRegular(status) {
32
+ if (status && (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1)) {
33
+ throw new Error('Unsafe .dev.vars path; replace its symlink, hard link or non-file before setup.')
34
+ }
35
+ }
@@ -0,0 +1,166 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { link, lstat, open, readFile, rename, unlink } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+ import { privateDirectory } from './files.mjs'
5
+ import { processStart } from './process-table.mjs'
6
+ import {processName} from '../process-diagnostics.mjs'
7
+
8
+ const busy = async (path) => {
9
+ let owner;try{owner=JSON.parse(await readFile(path,'utf8'));}catch{}
10
+ const pid=Number.isSafeInteger(owner?.pid)&&owner.pid>0?owner.pid:undefined;
11
+ const identity=pid?`PID ${pid} (${await processName(pid)})`:'an unidentified process';
12
+ return Object.assign(new Error(`An active local operation owns the state: ${identity}. Stop it with rer dev --stop or rer preview --stop; for an unmanaged process, stop PID ${pid ?? 'shown in the lock'} before retrying.`), {code: 'LOCAL_OPERATION_BUSY', ownerPid: pid});
13
+ }
14
+ const graceMs = 10000
15
+
16
+ // Setup holds this lock through its nested build and migration commands. A
17
+ // separate build/setup cannot restore credentials into another active build.
18
+ // The lock and every takeover guard record the owner's PID and start time, so a
19
+ // PID reused by an unrelated process never keeps a dead owner's lock alive.
20
+ export async function localOperation(root, inheritedToken) {
21
+ const directory = join(root, '.wrangler/local-operation')
22
+ await privateDirectory(root, directory)
23
+ const path = join(directory, 'active.json')
24
+ if (inheritedToken) {
25
+ let owner
26
+ try { owner = JSON.parse(await readFile(path, 'utf8')) } catch { /* Fixed diagnostic below. */ }
27
+ if (owner?.token !== inheritedToken || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
28
+ throw new Error('The parent local setup operation is no longer active. Run the command again.')
29
+ }
30
+ if (!await ownerAlive(owner)) throw new Error('The parent local setup process has stopped. Run the command again.')
31
+ return { token: inheritedToken, release: async () => {} }
32
+ }
33
+ const self = await currentProcess()
34
+ let file
35
+ for (let attempt = 0; !file; attempt++) {
36
+ try { file = await open(path, 'wx', 0o600) } catch (error) {
37
+ if (error.code !== 'EEXIST') throw error
38
+ // A killed process cannot release its lock. Take over only when the
39
+ // recorded process is gone; a half-written file gets a short grace.
40
+ if (attempt === 0 && await takeOver(path)) continue
41
+ throw await busy(path)
42
+ }
43
+ }
44
+ const token = randomUUID()
45
+ try { await file.writeFile(JSON.stringify({ ...self, token })) } finally { await file.close() }
46
+ const acquired = await identity(path)
47
+ return { token, release: async () => {
48
+ if (await identity(path) === acquired) await unlink(path).catch(missing)
49
+ } }
50
+ }
51
+
52
+ async function currentProcess() {
53
+ return { pid: process.pid, start: await processStart(process.pid) }
54
+ }
55
+
56
+ // A recorded owner is alive only while its PID exists and, when the record
57
+ // carries a start time, that PID still names the same process.
58
+ export async function ownerAlive(owner) {
59
+ try { process.kill(owner.pid, 0) } catch (error) {
60
+ if (error.code === 'ESRCH') return false
61
+ if (error.code !== 'EPERM') throw error
62
+ }
63
+ if (typeof owner.start !== 'string') return true
64
+ return await processStart(owner.pid) === owner.start
65
+ }
66
+
67
+ // Removing a stale lock by name alone could remove a lock another starter has
68
+ // just created. Every starter that judged the same stale file competes for one
69
+ // exclusive guard named after that file's identity; only the guard holder may
70
+ // remove it, and only while the path still names that exact file. A new lock
71
+ // has a new identity, so it can never be removed as stale.
72
+ async function takeOver(path) {
73
+ const judged = await staleIdentity(path)
74
+ if (judged === undefined) return false
75
+ if (judged === null) return true // Already gone: retry the exclusive create.
76
+ const guardPath = `${path}.takeover-${judged}`
77
+ let guard
78
+ for (let attempt = 0; !guard; attempt++) {
79
+ try { guard = await open(guardPath, 'wx', 0o600) } catch (error) {
80
+ if (error.code !== 'EEXIST') throw error
81
+ // Another starter is taking over; it, or a faster starter, owns the lock.
82
+ // A guard whose owner was killed is removed once, after its grace.
83
+ if (attempt > 0 || !await removeAbandonedGuard(guardPath, path)) return false
84
+ }
85
+ }
86
+ try {
87
+ await guard.writeFile(JSON.stringify(await currentProcess()))
88
+ const current = await identity(path)
89
+ if (current === judged) await unlink(path).catch(missing)
90
+ return true
91
+ } finally {
92
+ await guard.close()
93
+ await unlink(guardPath).catch(missing)
94
+ }
95
+ }
96
+
97
+ // Returns the identity of a stale lock, null when the lock is already gone, or
98
+ // undefined when a live (or freshly created) lock holds the path. Judge and
99
+ // identify through one descriptor so the verdict belongs to that exact file.
100
+ async function staleIdentity(path) {
101
+ let handle
102
+ try { handle = await open(path, 'r') } catch (error) {
103
+ if (error.code === 'ENOENT') return null
104
+ throw error
105
+ }
106
+ try {
107
+ const status = await handle.stat({ bigint: true })
108
+ const id = key(status)
109
+ try {
110
+ const owner = JSON.parse(await handle.readFile('utf8'))
111
+ if (Number.isSafeInteger(owner.pid) && owner.pid > 0) return await ownerAlive(owner) ? undefined : id
112
+ } catch { /* Half-written or unreadable: judged by age below. */ }
113
+ return Date.now() - Number(status.mtimeMs) > graceMs ? id : undefined
114
+ } finally { await handle.close() }
115
+ }
116
+
117
+ // A takeover guard left by a killed starter would otherwise block every later
118
+ // build. After the grace, a guard whose recorded owner is gone (or that was
119
+ // never written) is moved aside and removed. The move is checked against the
120
+ // judged file; a guard created meanwhile is put back, never discarded.
121
+ async function removeAbandonedGuard(guardPath, lockPath) {
122
+ let handle
123
+ try { handle = await open(guardPath, 'r') } catch (error) {
124
+ if (error.code === 'ENOENT') return true
125
+ throw error
126
+ }
127
+ let judged
128
+ try {
129
+ const status = await handle.stat({ bigint: true })
130
+ if (Date.now() - Number(status.mtimeMs) <= graceMs) return false
131
+ let owner
132
+ try { owner = JSON.parse(await handle.readFile('utf8')) } catch { /* Never written: abandoned. */ }
133
+ if (Number.isSafeInteger(owner?.pid) && owner.pid > 0 && await ownerAlive(owner)) {
134
+ throw new Error(`A stale-lock takeover has held ${guardPath} for more than ${graceMs / 1000} s. If no local setup or build is running, delete it and ${lockPath}, then run again.`)
135
+ }
136
+ judged = movedKey(status)
137
+ } finally { await handle.close() }
138
+ const aside = `${guardPath}.abandoned-${randomUUID()}`
139
+ try { await rename(guardPath, aside) } catch (error) {
140
+ if (error.code === 'ENOENT') return true
141
+ throw error
142
+ }
143
+ if (movedKey(await lstat(aside, { bigint: true })) !== judged) {
144
+ // Another starter replaced the abandoned guard first. Restore its guard
145
+ // without replacing a newer one, and let that starter finish.
146
+ await link(aside, guardPath).catch((error) => { if (error.code !== 'EEXIST') throw error })
147
+ await unlink(aside)
148
+ return false
149
+ }
150
+ await unlink(aside)
151
+ return true
152
+ }
153
+
154
+ const missing = (error) => { if (error.code !== 'ENOENT') throw error }
155
+
156
+ async function identity(path) {
157
+ try { return key(await lstat(path, { bigint: true })) } catch (error) {
158
+ if (error.code === 'ENOENT') return null
159
+ throw error
160
+ }
161
+ }
162
+
163
+ // Inode numbers can be reused after deletion; the change time cannot repeat.
164
+ const key = (status) => `${status.dev}-${status.ino}-${status.ctimeNs}`
165
+ // A rename changes ctime, so a moved file is matched by its unchanged fields.
166
+ const movedKey = (status) => `${status.dev}-${status.ino}-${status.birthtimeNs}-${status.mtimeNs}`
@@ -0,0 +1,34 @@
1
+ import {readdir,lstat} from 'node:fs/promises';
2
+ import {join} from 'node:path';
3
+ import {DatabaseSync} from 'node:sqlite';
4
+
5
+ // Read only the canonical local D1 files, before settings/build/migrations can
6
+ // mutate anything. An unrelated database does not establish an owner claim.
7
+ export async function localOwners(root){
8
+ const found=[];
9
+ let directory=root;
10
+ for(const part of ['.wrangler','state','v3','d1','miniflare-D1DatabaseObject']){
11
+ directory=join(directory,part);let status;try{status=await lstat(directory);}catch(error){if(error.code==='ENOENT')return []; throw error;}
12
+ if(status.isSymbolicLink()||!status.isDirectory())throw Error('Canonical local state is unsafe. Preserve it and recover its placement before setup.');
13
+ }
14
+ for(const file of await readdir(directory,{withFileTypes:true})){
15
+ if(!file.isFile()||!file.name.endsWith('.sqlite'))continue;
16
+ let db,owners=[];
17
+ try{db=new DatabaseSync(join(directory,file.name),{readOnly:true});
18
+ const tables=new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(row=>row.name));
19
+ if(tables.has('oa_operators'))owners=db.prepare("SELECT email FROM oa_operators WHERE role='owner' AND status='active'").all();
20
+ }finally{db?.close();}
21
+ found.push(...owners.map(owner=>owner.email).filter(email=>typeof email==='string'));
22
+ }
23
+ return [...new Set(found)];
24
+ }
25
+ export async function assertLocalOwner(root,email,failure=message=>Error(message),inspect,setting='private owner email setting'){
26
+ const owners=inspect?await inspect(root):await localOwners(root);
27
+ if(!Array.isArray(owners)||owners.some(owner=>typeof owner!=='string'))throw failure('The localOwner hook must return existing owner email addresses without changing state.');
28
+ if(owners.length&&!owners.some(owner=>owner.toLowerCase()===email.toLowerCase())){
29
+ const address=safeOwner(owners[0]);
30
+ throw failure(`Local database already belongs to ${address}. Restore ${setting} to ${address}, then run rer setup --owner-email ${address}; or back up data and run rer reset --yes. Existing data and private settings were kept.`);
31
+ }
32
+ return owners;
33
+ }
34
+ export const safeOwner=email=>email.length<=254&&/^[a-zA-Z0-9.!+_%\-]+@[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)+$/.test(email)?email:'<existing-owner-email>';
@@ -0,0 +1,114 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { execFile } from 'node:child_process'
3
+ import { readFile, readdir } from 'node:fs/promises'
4
+ import { promisify } from 'node:util'
5
+
6
+ const execute = promisify(execFile)
7
+ export const validPid = (pid) => Number.isSafeInteger(pid) && pid > 0 && pid <= 0x7fffffff
8
+
9
+ // Inspect only numeric process metadata: command lines can contain credentials.
10
+ // Linux's start ticks distinguish recycled PIDs more precisely than ps lstart.
11
+
12
+ export function parseLinuxProcess(pid, pgid, text) {
13
+ if (!validPid(pid) || !validPid(pgid) || !text.startsWith(`${pid} (`) || !text.includes(') ')) {
14
+ throw new Error('Invalid process identity.')
15
+ }
16
+ const fields = text.slice(text.lastIndexOf(')') + 2).trim().split(/\s+/)
17
+ const ppid = Number(fields[1])
18
+ if (Number(fields[2]) !== pgid) return
19
+ if (!Number.isSafeInteger(ppid) || ppid < 0 || !/^\d+$/.test(fields[19])) {
20
+ throw new Error('Invalid process identity.')
21
+ }
22
+ return { pid, ppid, pgid, state: fields[0], birth: fields[19] }
23
+ }
24
+
25
+ async function processGroup(pgid, signal, deadline) {
26
+ if (process.platform === 'linux') {
27
+ // Minimal Linux containers need not install ps. Procfs provides exact
28
+ // start ticks and includes zombies until their actual parent reaps them.
29
+ const pids = (await readdir('/proc')).filter((name) => /^\d+$/.test(name)).map(Number).filter(validPid)
30
+ const group = []
31
+ // Procfs metadata reads are tiny kernel snapshots. Avoid hundreds of
32
+ // thread-pool round trips delaying cooperative shutdown; retain an explicit
33
+ // deadline and every PID/start-tick check while scanning the same inventory.
34
+ for (const pid of pids) {
35
+ signal.throwIfAborted()
36
+ if (Date.now() >= deadline) throw new Error('Process inspection deadline expired.')
37
+ let text
38
+ try { text = readFileSync(`/proc/${pid}/stat`, 'utf8') } catch (error) {
39
+ if (error.code === 'ENOENT' || error.code === 'ESRCH') continue
40
+ throw error
41
+ }
42
+ const member = parseLinuxProcess(pid, pgid, text)
43
+ if (member) group.push(member)
44
+ }
45
+ return group
46
+ }
47
+ const { stdout } = await execute('/bin/ps', [
48
+ '-ax', '-o', 'pid=', '-o', 'ppid=', '-o', 'pgid=', '-o', 'stat=', '-o', 'lstart=',
49
+ ], { env: { ...process.env, LC_ALL: 'C' }, signal, killSignal: 'SIGKILL', maxBuffer: 8 * 1024 * 1024 })
50
+ const group = []
51
+ for (const line of stdout.split('\n').filter((line) => line.trim())) {
52
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/)
53
+ if (!match) throw new Error('Could not read process identities.')
54
+ const [, id, parent, groupId, state, birth] = match
55
+ const pid = Number(id)
56
+ const ppid = Number(parent)
57
+ if (Number(groupId) !== pgid) continue
58
+ if (!validPid(pid) || !Number.isSafeInteger(ppid) || ppid < 0 || !Number.isFinite(Date.parse(birth))) {
59
+ throw new Error('Invalid process identity.')
60
+ }
61
+ group.push({ pid, ppid, pgid, state, birth })
62
+ }
63
+ return group
64
+ }
65
+
66
+ export async function readOwnedGroup(pgid, timeout = 1000) {
67
+ if (!validPid(pgid)) throw new Error('Invalid owned process group.')
68
+ if (!(timeout > 0)) throw new Error('Process inspection deadline expired.')
69
+ const controller = new AbortController()
70
+ let timer
71
+ try {
72
+ return await Promise.race([processGroup(pgid, controller.signal, Date.now() + timeout), new Promise((_, reject) => {
73
+ timer = setTimeout(() => {
74
+ controller.abort()
75
+ reject(new Error('Process inspection deadline expired.'))
76
+ }, timeout)
77
+ })])
78
+ } finally { clearTimeout(timer) }
79
+ }
80
+
81
+ export const sameProcess = (left, right) => left?.pid === right?.pid
82
+ && left?.pgid === right?.pgid && left?.birth === right?.birth
83
+
84
+ // A process start time distinguishes a live owner from an unrelated process that
85
+ // reused its PID. Linux reports start ticks since boot; elsewhere ps reports a
86
+ // one-second start time, read in UTC so every caller sees the same value.
87
+ // Returns undefined when no process has this PID.
88
+ export async function processStart(pid) {
89
+ if (!validPid(pid)) throw new Error('Invalid process identity.')
90
+ if (process.platform === 'linux') {
91
+ let text
92
+ try { text = await readFile(`/proc/${pid}/stat`, 'utf8') } catch (error) {
93
+ if (error.code === 'ENOENT' || error.code === 'ESRCH') return undefined
94
+ throw error
95
+ }
96
+ const fields = text.slice(text.lastIndexOf(')') + 2).trim().split(/\s+/)
97
+ if (!/^\d+$/.test(fields[19] ?? '')) throw new Error('Invalid process identity.')
98
+ return `linux:${fields[19]}`
99
+ }
100
+ let stdout
101
+ try {
102
+ ({ stdout } = await execute('/bin/ps', ['-o', 'lstart=', '-p', String(pid)], {
103
+ env: { ...process.env, LC_ALL: 'C', TZ: 'UTC' }, killSignal: 'SIGKILL', timeout: 5000,
104
+ }))
105
+ } catch (error) {
106
+ // ps exits 1 when the PID does not exist.
107
+ if (error.code === 1 && !String(error.stdout ?? '').trim()) return undefined
108
+ throw error
109
+ }
110
+ if (!stdout.trim()) return undefined
111
+ const started = Date.parse(`${stdout.trim()} UTC`)
112
+ if (!Number.isFinite(started)) throw new Error('Invalid process identity.')
113
+ return `ps:${started}`
114
+ }