@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.
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/bin/rer.mjs +3 -0
- package/cli/README.md +243 -0
- package/cli/adapter.mjs +10 -0
- package/cli/ai-local.mjs +65 -0
- package/cli/archive-path.mjs +52 -0
- package/cli/archive-vite.mjs +55 -0
- package/cli/archive.mjs +8 -0
- package/cli/auth-copy.mjs +80 -0
- package/cli/bounded.mjs +70 -0
- package/cli/cache-doctor.mjs +63 -0
- package/cli/container-transfer.mjs +35 -0
- package/cli/container.mjs +62 -0
- package/cli/contract.mjs +25 -0
- package/cli/dependencies.mjs +45 -0
- package/cli/dependency-approval.mjs +33 -0
- package/cli/dependency-doctor.mjs +34 -0
- package/cli/dependency-input.mjs +43 -0
- package/cli/deploy-config.mjs +118 -0
- package/cli/deploy.mjs +37 -0
- package/cli/dev-identity.mjs +80 -0
- package/cli/errors.mjs +14 -0
- package/cli/export.mjs +42 -0
- package/cli/font-cache.mjs +29 -0
- package/cli/hosted-output.mjs +110 -0
- package/cli/independent.mjs +29 -0
- package/cli/inspect.mjs +174 -0
- package/cli/jsonc.mjs +111 -0
- package/cli/lifecycle.mjs +148 -0
- package/cli/local.mjs +100 -0
- package/cli/main.mjs +57 -0
- package/cli/managed-server.mjs +143 -0
- package/cli/node-runtime-cache.mjs +68 -0
- package/cli/node-runtime-discovery.mjs +56 -0
- package/cli/node-runtime-download.mjs +59 -0
- package/cli/node-runtime.mjs +50 -0
- package/cli/output.mjs +22 -0
- package/cli/parse.mjs +77 -0
- package/cli/port.mjs +52 -0
- package/cli/portability-worker.mjs +56 -0
- package/cli/portability.mjs +66 -0
- package/cli/preview-cache.mjs +99 -0
- package/cli/project.mjs +48 -0
- package/cli/release.mjs +24 -0
- package/cli/runtime-front-door.mjs +46 -0
- package/cli/scheduled.mjs +81 -0
- package/cli/server-activity.mjs +12 -0
- package/cli/server-proxy.mjs +93 -0
- package/cli/server-registry.mjs +182 -0
- package/cli/server-resources.mjs +22 -0
- package/cli/settings.mjs +15 -0
- package/cli/setup-answers.mjs +29 -0
- package/cli/setup-input.mjs +113 -0
- package/cli/shared.mjs +7 -0
- package/cli/transfers.mjs +33 -0
- package/cli/version.mjs +36 -0
- package/composition/git-source.mjs +111 -0
- package/edition-authoring/files.mjs +81 -0
- package/edition-authoring/manifest.mjs +65 -0
- package/global/README.md +158 -0
- package/global/checkout.mjs +72 -0
- package/global/cli/command.mjs +13 -0
- package/global/cli/install.mjs +75 -0
- package/global/cli/layout.mjs +45 -0
- package/global/cli/status.mjs +54 -0
- package/global/cli/uninstall.mjs +36 -0
- package/global/delegate.mjs +13 -0
- package/global/launch/capacity.mjs +72 -0
- package/global/launch/dev.mjs +117 -0
- package/global/launch/facts.mjs +94 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +63 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +106 -0
- package/global/launch/steps.mjs +90 -0
- package/global/launch/stop.mjs +89 -0
- package/global/launch/worker.mjs +62 -0
- package/global/list.mjs +59 -0
- package/global/main.mjs +53 -0
- package/global/package-info.mjs +38 -0
- package/global/registry.mjs +24 -0
- package/global/rer.mjs +29 -0
- package/global/update/archive.mjs +89 -0
- package/global/update/command.mjs +90 -0
- package/global/update/latest.mjs +6 -0
- package/global/update/notice.mjs +58 -0
- package/global/update/registry.mjs +47 -0
- package/global/version.mjs +29 -0
- package/install.sh +144 -0
- package/modules/ai/LICENSE +21 -0
- package/modules/ai/README.md +90 -0
- package/modules/ai/client.mjs +71 -0
- package/modules/ai/config.mjs +39 -0
- package/modules/ai/dev/index.d.mts +6 -0
- package/modules/ai/dev/index.mjs +64 -0
- package/modules/ai/dev/outbox.mjs +33 -0
- package/modules/ai/errors.mjs +28 -0
- package/modules/ai/fake/index.d.mts +3 -0
- package/modules/ai/fake/index.mjs +82 -0
- package/modules/ai/index.d.mts +10 -0
- package/modules/ai/index.mjs +3 -0
- package/modules/ai/package.json +6 -0
- package/modules/container/README.md +156 -0
- package/modules/container/adapters/d1.mjs +34 -0
- package/modules/container/adapters/egress.mjs +40 -0
- package/modules/container/adapters/jobs.mjs +47 -0
- package/modules/container/adapters/objects.mjs +84 -0
- package/modules/container/adapters/retained.mjs +14 -0
- package/modules/container/adapters/sqlite-error.mjs +7 -0
- package/modules/container/adapters/sqlite-worker.mjs +26 -0
- package/modules/container/assets.mjs +14 -0
- package/modules/container/bindings.mjs +1 -0
- package/modules/container/boundary.mjs +38 -0
- package/modules/container/build.mjs +65 -0
- package/modules/container/deploy-input.mjs +17 -0
- package/modules/container/entrypoint.sh +5 -0
- package/modules/container/host.mjs +43 -0
- package/modules/container/ingress.mjs +23 -0
- package/modules/container/lock.mjs +11 -0
- package/modules/container/migrations.mjs +16 -0
- package/modules/container/node-engine.mjs +39 -0
- package/modules/container/portability-schema.mjs +14 -0
- package/modules/container/remote/deploy.py +76 -0
- package/modules/container/remote/files.py +275 -0
- package/modules/container/remote/resources.py +157 -0
- package/modules/container/runner.mjs +10 -0
- package/modules/container/ssh-plan.mjs +44 -0
- package/modules/container/ssh.mjs +63 -0
- package/modules/container/transfer.mjs +27 -0
- package/modules/container/worker-engine.mjs +32 -0
- package/modules/dependencies/README.md +211 -0
- package/modules/dependencies/approval.mjs +51 -0
- package/modules/dependencies/auth.mjs +40 -0
- package/modules/dependencies/authority.mjs +14 -0
- package/modules/dependencies/crypto.mjs +13 -0
- package/modules/dependencies/index.d.mts +23 -0
- package/modules/dependencies/index.mjs +113 -0
- package/modules/dependencies/logout.mjs +42 -0
- package/modules/dependencies/native.mjs +9 -0
- package/modules/dependencies/protocol.mjs +23 -0
- package/modules/dependencies/schema/approval-attempt.sql +3 -0
- package/modules/dependencies/schema/dependencies.sql +15 -0
- package/modules/dependencies/store.mjs +19 -0
- package/modules/deploy-kit/README.md +308 -0
- package/modules/deploy-kit/artifact.mjs +47 -0
- package/modules/deploy-kit/build-modules.mjs +21 -0
- package/modules/deploy-kit/check.mjs +13 -0
- package/modules/deploy-kit/cli.mjs +50 -0
- package/modules/deploy-kit/container-config.mjs +8 -0
- package/modules/deploy-kit/default-stage.mjs +14 -0
- package/modules/deploy-kit/deploy.mjs +80 -0
- package/modules/deploy-kit/health-request.mjs +86 -0
- package/modules/deploy-kit/health.mjs +99 -0
- package/modules/deploy-kit/index.mjs +12 -0
- package/modules/deploy-kit/local-only.mjs +10 -0
- package/modules/deploy-kit/migrations.mjs +48 -0
- package/modules/deploy-kit/owner-cli.mjs +63 -0
- package/modules/deploy-kit/owner.mjs +297 -0
- package/modules/deploy-kit/prepare.mjs +62 -0
- package/modules/deploy-kit/provider.mjs +99 -0
- package/modules/deploy-kit/queue-consumer.mjs +10 -0
- package/modules/deploy-kit/receipt.mjs +16 -0
- package/modules/deploy-kit/render.mjs +47 -0
- package/modules/deploy-kit/resources.mjs +106 -0
- package/modules/deploy-kit/service-grants.mjs +25 -0
- package/modules/deploy-kit/sql-safety.mjs +94 -0
- package/modules/deploy-kit/sql-tokens.mjs +49 -0
- package/modules/deploy-kit/teardown.mjs +100 -0
- package/modules/deploy-kit/templates.mjs +31 -0
- package/modules/deploy-kit/validation.mjs +58 -0
- package/modules/deploy-kit/worker.mjs +105 -0
- package/modules/erasure/CONTRACT.md +90 -0
- package/modules/erasure/README.md +221 -0
- package/modules/erasure/audit.mjs +23 -0
- package/modules/erasure/blobs.mjs +45 -0
- package/modules/erasure/cascade.mjs +91 -0
- package/modules/erasure/completion.mjs +62 -0
- package/modules/erasure/declaration.mjs +51 -0
- package/modules/erasure/fence.mjs +19 -0
- package/modules/erasure/index.d.mts +62 -0
- package/modules/erasure/index.mjs +85 -0
- package/modules/erasure/order.mjs +56 -0
- package/modules/erasure/plan.mjs +121 -0
- package/modules/erasure/rate.mjs +57 -0
- package/modules/erasure/receipt.mjs +32 -0
- package/modules/erasure/root.mjs +21 -0
- package/modules/erasure/schema/erasure-rates.sql +5 -0
- package/modules/erasure/schema/erasure.sql +27 -0
- package/modules/erasure/scope.mjs +29 -0
- package/modules/erasure/sql.mjs +23 -0
- package/modules/erasure/verification.mjs +69 -0
- package/modules/integrations/core/CONTRACT.md +50 -0
- package/modules/integrations/core/README.md +27 -0
- package/modules/integrations/core/crypto.ts +19 -0
- package/modules/integrations/core/database.ts +21 -0
- package/modules/integrations/core/delivery.ts +97 -0
- package/modules/integrations/core/destination.ts +13 -0
- package/modules/integrations/core/index.ts +7 -0
- package/modules/integrations/core/node-transport.ts +72 -0
- package/modules/integrations/core/public-address.ts +57 -0
- package/modules/integrations/core/repository.ts +74 -0
- package/modules/integrations/core/schema.sql +36 -0
- package/modules/integrations/core/sqlite.ts +20 -0
- package/modules/integrations/core/timestamped-body-signature.ts +38 -0
- package/modules/integrations/core/tokens.ts +71 -0
- package/modules/integrations/core/types.ts +52 -0
- package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
- package/modules/integrations/core/webhook.ts +73 -0
- package/modules/job-queue/README.md +173 -0
- package/modules/job-queue/cli.mjs +64 -0
- package/modules/job-queue/command.mjs +64 -0
- package/modules/job-queue/config.mjs +48 -0
- package/modules/job-queue/identity.mjs +48 -0
- package/modules/job-queue/index.mjs +135 -0
- package/modules/job-queue/membership.mjs +30 -0
- package/modules/job-queue/module.json +10 -0
- package/modules/job-queue/policy.mjs +29 -0
- package/modules/job-queue/processes.mjs +80 -0
- package/modules/job-queue/resources.mjs +37 -0
- package/modules/job-queue/storage.mjs +64 -0
- package/modules/job-queue/supervisor.mjs +30 -0
- package/modules/job-queue/waiting.mjs +16 -0
- package/modules/mail/LICENSE +21 -0
- package/modules/mail/README.md +336 -0
- package/modules/mail/doctor.mjs +81 -0
- package/modules/mail/index.mjs +8 -0
- package/modules/mail/live-proof/README.md +113 -0
- package/modules/mail/live-proof/run.mjs +55 -0
- package/modules/mail/package.json +19 -0
- package/modules/mail/pnpm-lock.yaml +537 -0
- package/modules/mail/src/adapters/cloudflare/README.md +84 -0
- package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
- package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
- package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
- package/modules/mail/src/adapters/messaging/README.md +92 -0
- package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
- package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
- package/modules/mail/src/adapters/messaging/index.mjs +61 -0
- package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
- package/modules/mail/src/adapters/messaging/request.mjs +52 -0
- package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
- package/modules/mail/src/adapters/resend/README.md +120 -0
- package/modules/mail/src/adapters/resend/index.mjs +22 -0
- package/modules/mail/src/adapters/resend/response.mjs +54 -0
- package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
- package/modules/mail/src/adapters/resend/size.mjs +99 -0
- package/modules/mail/src/adapters/sendgrid/README.md +75 -0
- package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
- package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
- package/modules/mail/src/core/errors.mjs +9 -0
- package/modules/mail/src/core/transport.mjs +33 -0
- package/modules/mail/src/core/validation.mjs +172 -0
- package/modules/mail/src/development/index.d.mts +37 -0
- package/modules/mail/src/development/index.mjs +35 -0
- package/modules/mail/src/development/outbox.mjs +78 -0
- package/modules/mail/src/development/schema.sql +9 -0
- package/modules/mail/src/development/viewer-html.mjs +42 -0
- package/modules/mail/src/development/viewer.mjs +65 -0
- package/modules/mail/src/doctor/README.md +107 -0
- package/modules/mail/src/doctor/common.mjs +64 -0
- package/modules/mail/src/doctor/dns.mjs +102 -0
- package/modules/mail/src/doctor/index.mjs +28 -0
- package/modules/mail/src/doctor/probe.mjs +66 -0
- package/modules/mail/src/doctor/receive.mjs +81 -0
- package/modules/mail/src/doctor/records.mjs +45 -0
- package/modules/mail/src/events/README.md +268 -0
- package/modules/mail/src/events/d1-schema.sql +25 -0
- package/modules/mail/src/events/d1-storage.mjs +76 -0
- package/modules/mail/src/events/index.mjs +3 -0
- package/modules/mail/src/events/intake.mjs +201 -0
- package/modules/mail/src/events/storage.mjs +52 -0
- package/modules/mail/src/events/verify.mjs +92 -0
- package/modules/mail/src/inbound/README.md +137 -0
- package/modules/mail/src/inbound/bytes.mjs +91 -0
- package/modules/mail/src/inbound/headers.mjs +143 -0
- package/modules/mail/src/inbound/index.mjs +29 -0
- package/modules/mail/src/inbound/mime.mjs +36 -0
- package/modules/mail/src/inbound/multipart.mjs +27 -0
- package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
- package/modules/mail/src/inbound/resend.mjs +54 -0
- package/modules/mail/src/inbound/sendgrid.mjs +60 -0
- package/modules/mail/src/sender.mjs +33 -0
- package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
- package/modules/mail/src/sendgrid-email/index.mjs +86 -0
- package/modules/mail/src/unsubscribe/index.mjs +142 -0
- package/modules/operator-auth/README.md +599 -0
- package/modules/operator-auth/accounts/adapter.mjs +41 -0
- package/modules/operator-auth/accounts/admin.mjs +159 -0
- package/modules/operator-auth/accounts/credentials.mjs +27 -0
- package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
- package/modules/operator-auth/accounts/index.d.mts +9 -0
- package/modules/operator-auth/accounts/index.mjs +9 -0
- package/modules/operator-auth/accounts/native.d.mts +38 -0
- package/modules/operator-auth/accounts/native.mjs +171 -0
- package/modules/operator-auth/accounts/oa.mjs +90 -0
- package/modules/operator-auth/accounts/provider-control.mjs +50 -0
- package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
- package/modules/operator-auth/accounts/roles.mjs +26 -0
- package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
- package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
- package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
- package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
- package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
- package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
- package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
- package/modules/operator-auth/accounts/settings-page.mjs +4 -0
- package/modules/operator-auth/accounts/store.mjs +246 -0
- package/modules/operator-auth/branding.mjs +66 -0
- package/modules/operator-auth/config.mjs +35 -0
- package/modules/operator-auth/conformance/README.md +81 -0
- package/modules/operator-auth/conformance/index.d.mts +43 -0
- package/modules/operator-auth/conformance/index.mjs +300 -0
- package/modules/operator-auth/conformance/presentation.mjs +57 -0
- package/modules/operator-auth/crypto.mjs +17 -0
- package/modules/operator-auth/dev/badge.mjs +62 -0
- package/modules/operator-auth/dev/index.d.mts +14 -0
- package/modules/operator-auth/dev/index.mjs +137 -0
- package/modules/operator-auth/dev/personas.mjs +17 -0
- package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
- package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
- package/modules/operator-auth/dev-admission.d.mts +4 -0
- package/modules/operator-auth/dev-admission.mjs +10 -0
- package/modules/operator-auth/dev-gate.mjs +86 -0
- package/modules/operator-auth/docs/README.md +78 -0
- package/modules/operator-auth/docs/native-accounts.md +313 -0
- package/modules/operator-auth/docs/tanstack-start.md +218 -0
- package/modules/operator-auth/docs/vite-spa.md +144 -0
- package/modules/operator-auth/errors.mjs +9 -0
- package/modules/operator-auth/http.mjs +42 -0
- package/modules/operator-auth/identity/admission.mjs +105 -0
- package/modules/operator-auth/identity/after-confirm.mjs +18 -0
- package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
- package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
- package/modules/operator-auth/identity/email-link/index.mjs +128 -0
- package/modules/operator-auth/identity/email-link/message.mjs +16 -0
- package/modules/operator-auth/identity/index.d.mts +7 -0
- package/modules/operator-auth/identity/index.mjs +5 -0
- package/modules/operator-auth/identity/oidc/README.md +171 -0
- package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
- package/modules/operator-auth/identity/oidc/config.mjs +102 -0
- package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
- package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
- package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
- package/modules/operator-auth/identity/oidc/index.mjs +287 -0
- package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
- package/modules/operator-auth/identity/oidc/network.mjs +93 -0
- package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
- package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
- package/modules/operator-auth/identity/oidc/store.mjs +68 -0
- package/modules/operator-auth/identity/principal.mjs +28 -0
- package/modules/operator-auth/index.d.mts +223 -0
- package/modules/operator-auth/index.mjs +11 -0
- package/modules/operator-auth/input.mjs +55 -0
- package/modules/operator-auth/local-cli.mjs +16 -0
- package/modules/operator-auth/page.mjs +34 -0
- package/modules/operator-auth/policy.mjs +20 -0
- package/modules/operator-auth/routes.mjs +91 -0
- package/modules/operator-auth/service.mjs +152 -0
- package/modules/operator-auth/signin/index.mjs +67 -0
- package/modules/owner-claim/README.md +137 -0
- package/modules/owner-claim/idp.mjs +40 -0
- package/modules/owner-claim/index.d.mts +27 -0
- package/modules/owner-claim/index.mjs +39 -0
- package/modules/owner-claim/page.mjs +11 -0
- package/modules/owner-claim/schema/idp.sql +4 -0
- package/modules/owner-claim/schema/owner-claim.sql +18 -0
- package/modules/owner-claim/transaction.mjs +87 -0
- package/modules/owner-claim/verify.mjs +80 -0
- package/modules/portability/README.md +444 -0
- package/modules/portability/bridge.mjs +102 -0
- package/modules/portability/catalogue.mjs +118 -0
- package/modules/portability/cli-options.mjs +38 -0
- package/modules/portability/cli.mjs +56 -0
- package/modules/portability/copies.mjs +68 -0
- package/modules/portability/dev-data.mjs +32 -0
- package/modules/portability/diff.mjs +33 -0
- package/modules/portability/export.mjs +129 -0
- package/modules/portability/files.mjs +65 -0
- package/modules/portability/format.mjs +122 -0
- package/modules/portability/generated.mjs +60 -0
- package/modules/portability/hash.mjs +79 -0
- package/modules/portability/http.mjs +42 -0
- package/modules/portability/import.mjs +185 -0
- package/modules/portability/index.d.mts +58 -0
- package/modules/portability/index.mjs +22 -0
- package/modules/portability/journal.mjs +49 -0
- package/modules/portability/legacy-files.mjs +43 -0
- package/modules/portability/legacy-safety.mjs +27 -0
- package/modules/portability/lines.mjs +36 -0
- package/modules/portability/migration-history.mjs +18 -0
- package/modules/portability/migration-statements.mjs +23 -0
- package/modules/portability/payloads.mjs +52 -0
- package/modules/portability/provider-data.mjs +25 -0
- package/modules/portability/restore-safety.mjs +30 -0
- package/modules/portability/schema/portability.sql +25 -0
- package/modules/portability/storage.mjs +49 -0
- package/modules/release/README.md +212 -0
- package/modules/release/catalogue.mjs +79 -0
- package/modules/release/export.mjs +51 -0
- package/modules/release/produce.mjs +136 -0
- package/modules/release/public-child-transport-state.cjs +29 -0
- package/modules/release/public-child.mjs +71 -0
- package/modules/release/public-git.mjs +21 -0
- package/modules/release/publication-scan.mjs +22 -0
- package/modules/release/publish-handoff.mjs +37 -0
- package/modules/release/source.mjs +71 -0
- package/modules/release/test-summary.mjs +50 -0
- package/onboarding/README.md +163 -0
- package/onboarding/activity.mjs +31 -0
- package/onboarding/adapter-auth.mjs +88 -0
- package/onboarding/adapter-hosting.mjs +35 -0
- package/onboarding/adapter-labels.mjs +13 -0
- package/onboarding/adapter-settings.mjs +129 -0
- package/onboarding/adapter.mjs +238 -0
- package/onboarding/agent-handoff.mjs +9 -0
- package/onboarding/app-processes.mjs +21 -0
- package/onboarding/archive-output.d.mts +3 -0
- package/onboarding/archive-output.mjs +54 -0
- package/onboarding/command-custody.mjs +24 -0
- package/onboarding/command-recovery.mjs +8 -0
- package/onboarding/commands.mjs +166 -0
- package/onboarding/dependency-approval.mjs +36 -0
- package/onboarding/dev-identity.mjs +23 -0
- package/onboarding/email.mjs +24 -0
- package/onboarding/failures.mjs +42 -0
- package/onboarding/input-limits.mjs +7 -0
- package/onboarding/installer-inputs.mjs +24 -0
- package/onboarding/launcher.mjs +34 -0
- package/onboarding/local/build-files.mjs +64 -0
- package/onboarding/local/build.mjs +49 -0
- package/onboarding/local/environment.mjs +30 -0
- package/onboarding/local/files.mjs +35 -0
- package/onboarding/local/operation.mjs +166 -0
- package/onboarding/local/owner.mjs +34 -0
- package/onboarding/local/process-table.mjs +114 -0
- package/onboarding/local/recovery.mjs +79 -0
- package/onboarding/local/state.mjs +31 -0
- package/onboarding/metadata.mjs +7 -0
- package/onboarding/operator-install.mjs +45 -0
- package/onboarding/operator-installer.mjs +39 -0
- package/onboarding/operator-prerequisites.mjs +27 -0
- package/onboarding/package.json +6 -0
- package/onboarding/prefill.mjs +16 -0
- package/onboarding/private-files.mjs +97 -0
- package/onboarding/private.mjs +35 -0
- package/onboarding/process-cleanup.mjs +134 -0
- package/onboarding/process-diagnostics.mjs +91 -0
- package/onboarding/process-scope.mjs +118 -0
- package/onboarding/process-table.mjs +118 -0
- package/onboarding/process-tree.mjs +36 -0
- package/onboarding/production-modules.mjs +22 -0
- package/onboarding/production-options.mjs +40 -0
- package/onboarding/production-plan.mjs +8 -0
- package/onboarding/production-runtime.mjs +30 -0
- package/onboarding/project-vite.mjs +14 -0
- package/onboarding/providers.mjs +289 -0
- package/onboarding/queue.mjs +53 -0
- package/onboarding/queued-entry.mjs +34 -0
- package/onboarding/readiness.mjs +59 -0
- package/onboarding/redaction-jwt.mjs +32 -0
- package/onboarding/redaction.mjs +298 -0
- package/onboarding/schema/SETUP.schema.json +514 -0
- package/onboarding/schema/dependencies.mjs +35 -0
- package/onboarding/schema/json-schema.mjs +202 -0
- package/onboarding/schema/setup-validation.mjs +325 -0
- package/onboarding/secrets.mjs +21 -0
- package/onboarding/server.mjs +188 -0
- package/onboarding/setup-manifest.mjs +116 -0
- package/onboarding/state.mjs +46 -0
- package/onboarding/theme-files.mjs +37 -0
- package/onboarding/theme.mjs +118 -0
- package/onboarding/ui/activity.js +98 -0
- package/onboarding/ui/address-client.mjs +33 -0
- package/onboarding/ui/app.js +377 -0
- package/onboarding/ui/index.html +1 -0
- package/onboarding/ui/model-core.mjs +127 -0
- package/onboarding/ui/model.mjs +5 -0
- package/onboarding/ui/style.css +63 -0
- package/onboarding/ui/theme.css +37 -0
- package/onboarding/ui/workspace-art.js +30 -0
- package/onboarding/wrangler-auth.mjs +43 -0
- package/operations/README.md +246 -0
- package/operations/operations.mjs +168 -0
- package/operations/project-operations.mjs +157 -0
- package/package.json +11 -4
- package/scripts/publish-check/README.md +72 -0
- package/scripts/publish-check/checksum_maps.py +52 -0
- package/scripts/publish-check/cli.py +34 -0
- package/scripts/publish-check/command_arguments.py +51 -0
- package/scripts/publish-check/command_shell_quotes.py +41 -0
- package/scripts/publish-check/command_source_quotes.py +120 -0
- package/scripts/publish-check/content_identities.py +250 -0
- package/scripts/publish-check/copy_identities.py +41 -0
- package/scripts/publish-check/der.py +61 -0
- package/scripts/publish-check/destination.py +117 -0
- package/scripts/publish-check/env_values.py +74 -0
- package/scripts/publish-check/font_licence.py +23 -0
- package/scripts/publish-check/git_history.py +115 -0
- package/scripts/publish-check/github_refs.py +217 -0
- package/scripts/publish-check/gitleaks.py +72 -0
- package/scripts/publish-check/gitleaks.toml +40 -0
- package/scripts/publish-check/history_scan.py +176 -0
- package/scripts/publish-check/hit_location.py +18 -0
- package/scripts/publish-check/hygiene_config.py +42 -0
- package/scripts/publish-check/hygiene_text.py +11 -0
- package/scripts/publish-check/identity-recipe.mjs +54 -0
- package/scripts/publish-check/json_identity.py +79 -0
- package/scripts/publish-check/license-exceptions.json +11 -0
- package/scripts/publish-check/licenses.py +49 -0
- package/scripts/publish-check/policy.py +18 -0
- package/scripts/publish-check/project_identity.py +89 -0
- package/scripts/publish-check/provenance.py +91 -0
- package/scripts/publish-check/provenance_pins.py +33 -0
- package/scripts/publish-check/public_base.py +63 -0
- package/scripts/publish-check/public_hygiene.py +80 -0
- package/scripts/publish-check/publication-config.json +12 -0
- package/scripts/publish-check/publication_config.py +34 -0
- package/scripts/publish-check/report.py +52 -0
- package/scripts/publish-check/repository_commands.py +75 -0
- package/scripts/publish-check/repository_intent.py +52 -0
- package/scripts/publish-check/reviewed.py +41 -0
- package/scripts/publish-check/source_hashes.mjs +6 -0
- package/scripts/publish-check/tree_scan.py +88 -0
- package/scripts/publish-check/trust_bundle.py +87 -0
- 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
|
+
}
|