mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
ops(OPS52): migra a media de produção do Vercel Blob para o Garage S3
- payload.config.ts: vercelBlobStorage -> s3Storage (@payloadcms/storage-s3) com endpoint Garage (forcePathStyle, region garage); bucket privado — o staticHandler do plugin streama os objetos em /api/media/file/<filename>, mantendo o contrato de URL relativa das 40 rows pré-existentes - src/utilities/mediaStorage.ts: resolveS3StorageEnv fail-closed (S3_* parcial -> throw no boot com a lista exata; nenhuma -> storage local dev/test) - seed-posts: remove pre-delete do @vercel/blob (S3 sobrescreve nativamente) - worktree-env.mjs: s3EnvCopiedLines all-or-nothing (unit-testada) - envs S3_BUCKET/S3_ENDPOINT/S3_REGION/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY no .env.example/.env.test; cloud-setup.sh sem o sed morto do blob - deps: -storage-vercel-blob -@vercel/blob +storage-s3; importMap regenerado - docs: impl plan (revisão A->B: Garage v2 sem acesso anônimo na API S3), AGENTS.md, changelog 2026-08-18-ops52
This commit is contained in:
parent
0aec0d5040
commit
dabc00f46c
18 changed files with 724 additions and 84 deletions
|
|
@ -15,7 +15,6 @@ bash "${SCRIPT_DIR}/ensure-postgres.sh"
|
||||||
if [ ! -f .env ]; then
|
if [ ! -f .env ]; then
|
||||||
sed \
|
sed \
|
||||||
-e 's/YOUR_SECRET_HERE/cursor-cloud-local-secret/' \
|
-e 's/YOUR_SECRET_HERE/cursor-cloud-local-secret/' \
|
||||||
-e 's/YOUR_BLOB_TOKEN_HERE/vercel_blob_rw_cloud01_localplaceholder01/' \
|
|
||||||
.env.example > .env
|
.env.example > .env
|
||||||
echo "[cloud-setup] wrote .env from .env.example (local-only values)"
|
echo "[cloud-setup] wrote .env from .env.example (local-only values)"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
15
.env.example
15
.env.example
|
|
@ -7,7 +7,20 @@
|
||||||
DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo
|
DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo
|
||||||
|
|
||||||
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
||||||
BLOB_READ_WRITE_TOKEN=YOUR_BLOB_TOKEN_HERE
|
# Media storage (OPS52) — Garage S3 (homeserver). When ANY of the four required
|
||||||
|
# envs (S3_BUCKET, S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY) is set,
|
||||||
|
# ALL of them must be: a partial config aborts the boot with the exact missing
|
||||||
|
# names (fail-closed). When none is set, Payload falls back to local disk storage
|
||||||
|
# (dev/test). The bucket stays PRIVATE: the app proxies the files through its
|
||||||
|
# /media/file route (the plugin streams each object from Garage), so no public
|
||||||
|
# bucket/DNS is needed. Production: S3_BUCKET=teqo-media,
|
||||||
|
# S3_ENDPOINT=http://100.119.220.31:3900 (tailnet do homeserver). Dev may use its
|
||||||
|
# own bucket — never share the production bucket across environments.
|
||||||
|
S3_BUCKET=
|
||||||
|
S3_ENDPOINT=
|
||||||
|
S3_REGION=garage
|
||||||
|
S3_ACCESS_KEY_ID=
|
||||||
|
S3_SECRET_ACCESS_KEY=
|
||||||
# Canonical public origin used in absolute campaign invite links. In production
|
# Canonical public origin used in absolute campaign invite links. In production
|
||||||
# this must be an exact HTTPS non-local origin: no credentials, path other than
|
# this must be an exact HTTPS non-local origin: no credentials, path other than
|
||||||
# `/`, query, or fragment. Production ignores request headers and invite
|
# `/`, query, or fragment. Production ignores request headers and invite
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@
|
||||||
# TEQO_TEST_DATABASE_URL=postgresql://teqo:teqo@127.0.0.1:5433/teqo_test
|
# TEQO_TEST_DATABASE_URL=postgresql://teqo:teqo@127.0.0.1:5433/teqo_test
|
||||||
DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test
|
DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test
|
||||||
PAYLOAD_SECRET=test-only-secret-not-used-in-production
|
PAYLOAD_SECRET=test-only-secret-not-used-in-production
|
||||||
BLOB_READ_WRITE_TOKEN=
|
# No S3_* set → media storage falls back to local disk in tests.
|
||||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
||||||
|
|
||||||
**PUB5** — slogan da campanha "Mais saúde mais futuro" no hero e no rodapé da home (jorgesolla1313.com.br), no lugar de "Um mandato do tamanho da Bahia" (discurso do mandato vigente; o novo é da campanha 2026 — saúde é o tema-âncora do candidato). Troca de copy cirúrgica, sem redesenho nem migration: h1 do hero em duas linhas uppercase "MAIS SAÚDE / MAIS FUTURO" com `aria-label` acompanhando ("MAIS SAÚDE MAIS FUTURO"), segunda linha já em `font-black` (anatomia existente), e o rodapé em sentence case com ponto ("Mais saúde mais futuro."). Testes que pinavam a string antiga atualizados (`campaignHero.unit`, `campaignHome.unit`, `frontend.e2e`). Fora de escopo decidido no gate: cabeçalho de artigos ("O mandato do tamanho da Bahia") e docs históricos. Verificação: unit 2269 + int 791 verdes, e2e frontend 8/8 em prod mode (`E2E_PROD=1`, `.next-e2e`), lint/format/typecheck/cycles/knip/build verdes; render conferido por screenshot + DOM (hero duas linhas, rodapé com ponto). `/simplify` 3 reviewers: sem P0/P1; P2 corrigidos no impl plan (redação + line refs); débitos descartados por serem convenções pré-existentes ou decisões do gate.
|
**PUB5** — slogan da campanha "Mais saúde mais futuro" no hero e no rodapé da home (jorgesolla1313.com.br), no lugar de "Um mandato do tamanho da Bahia" (discurso do mandato vigente; o novo é da campanha 2026 — saúde é o tema-âncora do candidato). Troca de copy cirúrgica, sem redesenho nem migration: h1 do hero em duas linhas uppercase "MAIS SAÚDE / MAIS FUTURO" com `aria-label` acompanhando ("MAIS SAÚDE MAIS FUTURO"), segunda linha já em `font-black` (anatomia existente), e o rodapé em sentence case com ponto ("Mais saúde mais futuro."). Testes que pinavam a string antiga atualizados (`campaignHero.unit`, `campaignHome.unit`, `frontend.e2e`). Fora de escopo decidido no gate: cabeçalho de artigos ("O mandato do tamanho da Bahia") e docs históricos. Verificação: unit 2269 + int 791 verdes, e2e frontend 8/8 em prod mode (`E2E_PROD=1`, `.next-e2e`), lint/format/typecheck/cycles/knip/build verdes; render conferido por screenshot + DOM (hero duas linhas, rodapé com ponto). `/simplify` 3 reviewers: sem P0/P1; P2 corrigidos no impl plan (redação + line refs); débitos descartados por serem convenções pré-existentes ou decisões do gate.
|
||||||
|
|
||||||
|
**Recently resolved (2026-08-18):** **OPS52** — a media saiu do Vercel Blob para o Garage S3 do homeserver (bucket `teqo-media` + key `teqo-key`, já existentes no Garage v2.1.0). O plugin `@payloadcms/storage-vercel-blob` (e o `@vercel/blob` do seed) foi trocado por `@payloadcms/storage-s3` na config, no padrão R2 da doc do Payload (`endpoint` + `forcePathStyle: true`, region `garage`). **Decisão de arquitetura (revisão A→B durante a execução, gate com o humano):** o Garage v2.1.0 **não suporta acesso anônimo na API S3** (sem `--public-read`; source: "when we support anonymous access in the future"; testado: `anonymous`/`anon` → `NoSuchAccessKey`), então o bucket fica **privado** e a app **serve a media por proxy** — sem `disablePayloadAccessControl`, o `staticHandler` do plugin streama o objeto do Garage em `/api/media/file/<filename>` (range + etag/304 nativos) — o **mesmo contrato de URL relativa que as 40 rows pré-existentes já têm** (a recuperação dos arquivos é a Issue #10/OPS52-media). **Fail-closed:** `resolveS3StorageEnv` (`src/utilities/mediaStorage.ts`, unit-testado) — envs `S3_*` parciais → throw no boot com a lista exata de faltantes; nenhuma → storage local (dev/test). Envs novas: `S3_BUCKET`, `S3_ENDPOINT`, `S3_REGION` (default `garage`), `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` (`.env.example`, `.env.test`, worktree.mjs com cópia all-or-nothing). Seed de posts: pre-delete do Blob removido (o S3 sobrescreve o key determinístico nativamente — idempotência intacta). Prova de contrato real: PUT/GET/DELETE via SDK contra o Garage (tailnet `http://100.119.220.31:3900`), depois media criada pelo Payload local com S3 ativo → `media.url` relativa + `GET /api/media/file/...` → 200 image/png; bucket devolvido a 0 objects e `.env.local` do worktree limpo (sem credenciais de prod). Sem migration (config-only; importMap regenerado sem `VercelBlobClientUploadHandler`). Runbook de cutover registrado no impl plan: setar as 5 `S3_*` no `~/stack/teqo-1313.env` (endpoint tailnet) antes do próximo deploy (OPS53). Repo: `docs/plans/ops52-blob-vercel-para-garage-impl.md`; infra-solla `STATE.md` atualizado à parte.
|
||||||
|
|
||||||
**Recently resolved (2026-08-17):** **OPS51** — o banco de produção saiu do Neon para o Postgres do homeserver (`teqo_1313`, container `teqo-1313`), sem mudança de config: o `~/stack/teqo-1313.env` já apontava para o banco preparado (criado 16/08 com role própria + cadeia completa de 56 migrations + zero conteúdo — verificado), então restaurar = a produção "apareceu" no container. **Divergência deliberada da intenção (questão A/B validada como B):** rejeitado criar `teqo_prod` novo — o argumento "o banco do 1313 tem vida própria" não se sustenta (o site 1313 É a produção deste repo) e o OPS53 já trata `teqo_1313` como produção. Execução 100% no homeserver (PII nunca tocou a workstation): URL do Neon puxada do Vercel (`vercel env pull`, projeto `jorgesolla`, `DATABASE_URL_UNPOOLED` → `NEON_DATABASE_URL` em `~/stack/.env` chmod 600), `pg_dump -Fc` (Neon PG 17.10) → `/srv/hdd/backups/teqo-neon-pre-migracao/teqo-neon-full-20260817-204800.dump` (5,8 MB, sha256 `f12196f2…`), `pg_restore --no-owner --no-privileges` como a role `teqo_1313` (drop schema + grant, padrão do `db-pull`), verificado: contagens por tabela idênticas ao Neon (1919 contatos, 39 posts, 1487 assinaturas, 1484 subscrições, 35 campaign_users, 392 lideranças, 2 pledges, 435 municípios, 6 atividades; supporter segue 0 — blocker LGPD ainda de pé), sequences no topo (`last_value == max(id)`), objetos com dono `teqo_1313`, `payload migrate` no-op (56 = 56, serviço de maintenance `teqo-1313-migrate`), 3 credenciais WebAuthn + 34 sessions + 5 chaves Consent restauradas (RP ID não mudou — biometria segue valendo). Smoke sem deploy: `/`, artigo com conteúdo restaurado, `/campanha/login`, `/admin`, `POST /api/revalidate` (`posts` + `global_privacy-policy`), WebAuthn `login-options`, barreira 307 sem sessão — verdes; smoke de browser (login, biometria, convites) validado manualmente. **Rollback documentado:** Neon nunca foi escrito (só lido); voltar = re-apontar `DATABASE_URL` do `teqo-1313.env` para o Neon + `compose up -d`. **Débito registrado (→ OPS52):** as 40 rows de `media` têm URLs relativas (`/api/media/file/…`) sem arquivos em lugar nenhum — capas dão 500 no homeserver e **404 no próprio Vercel/produção atual** (estado pré-existente; migração manteve paridade). Repo: plano da intenção concluído, `AGENTS.md` e help de `db:pull`/`check-push-chain` sem a referência ao Neon; infra-solla `STATE.md` atualizado à parte.
|
**Recently resolved (2026-08-17):** **OPS51** — o banco de produção saiu do Neon para o Postgres do homeserver (`teqo_1313`, container `teqo-1313`), sem mudança de config: o `~/stack/teqo-1313.env` já apontava para o banco preparado (criado 16/08 com role própria + cadeia completa de 56 migrations + zero conteúdo — verificado), então restaurar = a produção "apareceu" no container. **Divergência deliberada da intenção (questão A/B validada como B):** rejeitado criar `teqo_prod` novo — o argumento "o banco do 1313 tem vida própria" não se sustenta (o site 1313 É a produção deste repo) e o OPS53 já trata `teqo_1313` como produção. Execução 100% no homeserver (PII nunca tocou a workstation): URL do Neon puxada do Vercel (`vercel env pull`, projeto `jorgesolla`, `DATABASE_URL_UNPOOLED` → `NEON_DATABASE_URL` em `~/stack/.env` chmod 600), `pg_dump -Fc` (Neon PG 17.10) → `/srv/hdd/backups/teqo-neon-pre-migracao/teqo-neon-full-20260817-204800.dump` (5,8 MB, sha256 `f12196f2…`), `pg_restore --no-owner --no-privileges` como a role `teqo_1313` (drop schema + grant, padrão do `db-pull`), verificado: contagens por tabela idênticas ao Neon (1919 contatos, 39 posts, 1487 assinaturas, 1484 subscrições, 35 campaign_users, 392 lideranças, 2 pledges, 435 municípios, 6 atividades; supporter segue 0 — blocker LGPD ainda de pé), sequences no topo (`last_value == max(id)`), objetos com dono `teqo_1313`, `payload migrate` no-op (56 = 56, serviço de maintenance `teqo-1313-migrate`), 3 credenciais WebAuthn + 34 sessions + 5 chaves Consent restauradas (RP ID não mudou — biometria segue valendo). Smoke sem deploy: `/`, artigo com conteúdo restaurado, `/campanha/login`, `/admin`, `POST /api/revalidate` (`posts` + `global_privacy-policy`), WebAuthn `login-options`, barreira 307 sem sessão — verdes; smoke de browser (login, biometria, convites) validado manualmente. **Rollback documentado:** Neon nunca foi escrito (só lido); voltar = re-apontar `DATABASE_URL` do `teqo-1313.env` para o Neon + `compose up -d`. **Débito registrado (→ OPS52):** as 40 rows de `media` têm URLs relativas (`/api/media/file/…`) sem arquivos em lugar nenhum — capas dão 500 no homeserver e **404 no próprio Vercel/produção atual** (estado pré-existente; migração manteve paridade). Repo: plano da intenção concluído, `AGENTS.md` e help de `db:pull`/`check-push-chain` sem a referência ao Neon; infra-solla `STATE.md` atualizado à parte.
|
||||||
|
|
||||||
**Recently resolved (2026-08-17):** **OPS53** — o deploy do site 1313 virou etapa do CI: mergir em `main` com a suite verde == site atualizado em `jorgesolla1313.com.br`, sem passo manual. O job `deploy` em `.forgejo/workflows/ci.yml` roda no host do runner (workstation, `runs-on: host`) e streama `scripts/deploy-homeserver.sh` para o homeserver via SSH (`ssh homeserver "bash -s -- <sha>"`); o script remoto (bash puro, homeserver sem dependências novas) faz: guard de HEAD (`git ls-remote` de `main` == SHA do job, senão skip "stale"), `flock` de serialização, workspace `~/teqo-deploy` (clone do Forgejo local), `docker login localhost:5000` (REGISTRY_USER/PASSWORD de `~/stack/.env`), build dos stages `runner`+`migrator` no homeserver (`--network stack_default` para a geração estática alcançar o `postgres:5432` do compose; secrets de `~/stack/teqo-1313.env` via BuildKit, sem camadas/logs), push no registry local, swap dos tags de imagem no compose (com backup `docker-compose.yml.pre-<sha>`), **migrate pelo serviço de maintenance `teqo-1313-migrate` antes do rollout**, `docker compose up -d`, espera do healthcheck e smoke (`/`, `/campanha/login`, `/admin`, barreira 307, WebAuthn `login-options` com body JSON, `POST /api/revalidate` com o secret real — rehearsal validado contra o site vivo). Falha pós-swap → rollback automático best-effort (restore do backup + `up -d` da imagem anterior) + job vermelho. **Divergência deliberada da intenção (fato novo):** a intenção assumia o runner no homeserver; verificado (`infra-solla/STATE.md` + ao vivo) que o runner é a `workstation-runner` na workstation e o stack de produção vive no homeserver — a abordagem A foi corrigida para "job `runs-on: host` na workstation + todo o deploy no homeserver via SSH", o que manteve **zero secrets novos no Forgejo** e zero mudança de infra no compose. Nenhum secret no repo; envs seguem chmod-600 no homeserver. Runbook (rollback manual, falhas conhecidas, decisão de segurança do host label): `docs/ops/teqo-1313-deploy.md` (novo). Guarda estrutural nova: `tests/unit/deployScript.unit.spec.ts` (bash -n + invariantes do pipeline: HEAD guard, BuildKit secrets, ordem migrate→rollout, smoke). AGENTS.md/AGENT-OPS.md atualizados. Primeiro deploy real = o merge deste próprio item (tracer bullet); infra-solla `STATE.md` anotado à parte.
|
**Recently resolved (2026-08-17):** **OPS53** — o deploy do site 1313 virou etapa do CI: mergir em `main` com a suite verde == site atualizado em `jorgesolla1313.com.br`, sem passo manual. O job `deploy` em `.forgejo/workflows/ci.yml` roda no host do runner (workstation, `runs-on: host`) e streama `scripts/deploy-homeserver.sh` para o homeserver via SSH (`ssh homeserver "bash -s -- <sha>"`); o script remoto (bash puro, homeserver sem dependências novas) faz: guard de HEAD (`git ls-remote` de `main` == SHA do job, senão skip "stale"), `flock` de serialização, workspace `~/teqo-deploy` (clone do Forgejo local), `docker login localhost:5000` (REGISTRY_USER/PASSWORD de `~/stack/.env`), build dos stages `runner`+`migrator` no homeserver (`--network stack_default` para a geração estática alcançar o `postgres:5432` do compose; secrets de `~/stack/teqo-1313.env` via BuildKit, sem camadas/logs), push no registry local, swap dos tags de imagem no compose (com backup `docker-compose.yml.pre-<sha>`), **migrate pelo serviço de maintenance `teqo-1313-migrate` antes do rollout**, `docker compose up -d`, espera do healthcheck e smoke (`/`, `/campanha/login`, `/admin`, barreira 307, WebAuthn `login-options` com body JSON, `POST /api/revalidate` com o secret real — rehearsal validado contra o site vivo). Falha pós-swap → rollback automático best-effort (restore do backup + `up -d` da imagem anterior) + job vermelho. **Divergência deliberada da intenção (fato novo):** a intenção assumia o runner no homeserver; verificado (`infra-solla/STATE.md` + ao vivo) que o runner é a `workstation-runner` na workstation e o stack de produção vive no homeserver — a abordagem A foi corrigida para "job `runs-on: host` na workstation + todo o deploy no homeserver via SSH", o que manteve **zero secrets novos no Forgejo** e zero mudança de infra no compose. Nenhum secret no repo; envs seguem chmod-600 no homeserver. Runbook (rollback manual, falhas conhecidas, decisão de segurança do host label): `docs/ops/teqo-1313-deploy.md` (novo). Guarda estrutural nova: `tests/unit/deployScript.unit.spec.ts` (bash -n + invariantes do pipeline: HEAD guard, BuildKit secrets, ordem migrate→rollout, smoke). AGENTS.md/AGENT-OPS.md atualizados. Primeiro deploy real = o merge deste próprio item (tracer bullet); infra-solla `STATE.md` anotado à parte.
|
||||||
|
|
|
||||||
1
docs/changelog/2026-08-18-ops52.md
Normal file
1
docs/changelog/2026-08-18-ops52.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
**Recently resolved (2026-08-18):** **OPS52** — a media saiu do Vercel Blob para o Garage S3 do homeserver (bucket `teqo-media` + key `teqo-key`, já existentes no Garage v2.1.0). O plugin `@payloadcms/storage-vercel-blob` (e o `@vercel/blob` do seed) foi trocado por `@payloadcms/storage-s3` na config, no padrão R2 da doc do Payload (`endpoint` + `forcePathStyle: true`, region `garage`). **Decisão de arquitetura (revisão A→B durante a execução, gate com o humano):** o Garage v2.1.0 **não suporta acesso anônimo na API S3** (sem `--public-read`; source: "when we support anonymous access in the future"; testado: `anonymous`/`anon` → `NoSuchAccessKey`), então o bucket fica **privado** e a app **serve a media por proxy** — sem `disablePayloadAccessControl`, o `staticHandler` do plugin streama o objeto do Garage em `/api/media/file/<filename>` (range + etag/304 nativos) — o **mesmo contrato de URL relativa que as 40 rows pré-existentes já têm** (a recuperação dos arquivos é a Issue #10/OPS52-media). **Fail-closed:** `resolveS3StorageEnv` (`src/utilities/mediaStorage.ts`, unit-testado) — envs `S3_*` parciais → throw no boot com a lista exata de faltantes; nenhuma → storage local (dev/test). Envs novas: `S3_BUCKET`, `S3_ENDPOINT`, `S3_REGION` (default `garage`), `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` (`.env.example`, `.env.test`, worktree.mjs com cópia all-or-nothing). Seed de posts: pre-delete do Blob removido (o S3 sobrescreve o key determinístico nativamente — idempotência intacta). Prova de contrato real: PUT/GET/DELETE via SDK contra o Garage (tailnet `http://100.119.220.31:3900`), depois media criada pelo Payload local com S3 ativo → `media.url` relativa + `GET /api/media/file/...` → 200 image/png; bucket devolvido a 0 objects e `.env.local` do worktree limpo (sem credenciais de prod). Sem migration (config-only; importMap regenerado sem `VercelBlobClientUploadHandler`). Runbook de cutover registrado no impl plan: setar as 5 `S3_*` no `~/stack/teqo-1313.env` (endpoint tailnet) antes do próximo deploy (OPS53). Repo: `docs/plans/ops52-blob-vercel-para-garage-impl.md`; infra-solla `STATE.md` atualizado à parte.
|
||||||
122
docs/plans/ops52-blob-vercel-para-garage-impl.md
Normal file
122
docs/plans/ops52-blob-vercel-para-garage-impl.md
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
# Impl: Migrar a media de produção do Vercel Blob para o Garage S3
|
||||||
|
|
||||||
|
Status: aprovado (revisado 2026-08-18 — A→B: ver "Revisão em execução")
|
||||||
|
Atualizado em: 2026-08-18
|
||||||
|
Issue: #3 (OPS52)
|
||||||
|
Intenção: docs/plans/ops52-blob-vercel-para-garage.md
|
||||||
|
Appetite restante: herdado (~1 dia)
|
||||||
|
|
||||||
|
## Revisão em execução (2026-08-18, gate com o humano)
|
||||||
|
|
||||||
|
**A premissa da opção A caiu:** o Garage v2.1.0 **não suporta acesso anônimo na API
|
||||||
|
S3** — não há `--public-read` no CLI (`bucket allow` exige key real; testei
|
||||||
|
`anonymous`/`anon` → `NoSuchAccessKey`) e o source confirma ("when we support
|
||||||
|
anonymous access in the future"). O único serving público é o website mode
|
||||||
|
(`s3_web` 3902 + root_domain + DNS/cert) — infra fora do apetite.
|
||||||
|
|
||||||
|
**Revisão para B (aprovada pelo humano, "desde que funcione"):** bucket **privado**;
|
||||||
|
sem `disablePayloadAccessControl`, o plugin injeta o `staticHandler` do S3 nos
|
||||||
|
handlers da collection — o Payload serve `/media/file/<filename>` streamando o
|
||||||
|
objeto do Garage (headObject + getObject via SDK, range/etag-304 nativos).
|
||||||
|
`media.url` continua **relativa** (`/media/file/<filename>`) — o MESMO contrato que
|
||||||
|
as 40 rows já têm → a OPS52-media (#10) restaura os objetos e o site renderiza
|
||||||
|
sem reescrever banco. `S3_PUBLIC_URL` foi **removida** das envs (não gera mais URL
|
||||||
|
absoluta). Tráfego de mídia passa pelo container (irrisório: ~40 capas).
|
||||||
|
|
||||||
|
**O corpo abaixo já reflete a opção B.**
|
||||||
|
|
||||||
|
## Leitura da intenção
|
||||||
|
|
||||||
|
- **Outcome:** media inteiramente no Garage S3 (uploads novos + seed), sem dependência do Vercel Blob; guardrail de que cada ambiente aponta para o bucket certo; upload sem credencial falha com erro claro, nunca grava em lugar errado.
|
||||||
|
- **O que NÃO negociar:** fail-closed sem credencial de storage; seed idempotente; URLs públicas estáveis (posts/emails antigos não podem quebrar por causa da troca).
|
||||||
|
- **O que reavaliar:**
|
||||||
|
- _"Objetos existentes migrados do Blob"_ — **falso por evidência**: o achado da OPS51 (documentado na Issue #10) é que as 40 rows de `media` têm URLs **relativas** (`/api/media/file/…`) e os arquivos não existem em lugar nenhum (404 até no Vercel atual). **Não há URLs do domínio do Blob embutidas no conteúdo publicado** → o anti-goal "manter dois storages vivos" e a questão "redirect do domínio antigo" **desaparecem**: não há nada a redirecionar. A recuperação dos arquivos é a Issue #10 (dependente desta); esta entrega deixa o storage pronto.
|
||||||
|
- _"Contrato de URL pública"_ — com a opção B, o contrato é o **mesmo de hoje**: `media.url` relativa (`/api/media/file/<filename>`), servida pelo proxy do Payload; o frontend (que usa `media.url` direto como `src` e `toAbsoluteUrl` para OG) continua funcionando sem reescrita.
|
||||||
|
- _"Store compartilhada entre ambientes"_ — hoje `BLOB_READ_WRITE_TOKEN` é compartilhado; o próprio prod (`~/stack/teqo-1313.env`) **não tem token nenhum** hoje. A troca resolve por construção: dev sem `S3_*` → storage local; com `S3_*` → bucket explícito por ambiente.
|
||||||
|
|
||||||
|
## Abordagem recomendada
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[admin upload] --> P[payload.create media]
|
||||||
|
P -->|sem S3_* envs| L[storage LOCAL<br/>media/ dir, dev/test]
|
||||||
|
P -->|S3_* envs completas| S3[s3Storage plugin<br/>@payloadcms/storage-s3]
|
||||||
|
S3 --> G[Garage homeserver<br/>bucket teqo-media PRIVADO]
|
||||||
|
B[navegador] -->|GET /api/media/file/<file>| X[Payload staticHandler]
|
||||||
|
X -->|GetObject SDK| G
|
||||||
|
D[seed-posts] -->|payload.create| S3
|
||||||
|
D -->|overwrite nativo do S3| I[idempotente por filename]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Opções consideradas:** A (URL direta no bucket — inviável: Garage v2 sem acesso anônimo) | B (proxy do Payload via staticHandler do plugin — **adotada**) | C (adapter custom via `@payloadcms/plugin-cloud-storage`)
|
||||||
|
**Recomendação:** **B** — `s3Storage` do `@payloadcms/storage-s3` sem `disablePayloadAccessControl`: o plugin injeta o `staticHandler` que streama o objeto do Garage em `/media/file/<filename>` (range/etag-304 nativos), mantendo o contrato de URL relativa e o bucket privado. Zero infra nova (sem DNS/túnel/public-read).
|
||||||
|
**Rejeitadas:** A (impossível no Garage v2.1.0: sem acesso anônimo na API S3; website mode exigiria expor 3902 + root_domain + DNS/cert — infra fora do apetite); C (adapter custom reimplementa handleUpload/handleDelete/generateURL do zero — o plugin oficial já faz; custo sem ganho).
|
||||||
|
|
||||||
|
### Componentes / mudanças
|
||||||
|
|
||||||
|
- **`src/payload.config.ts`**: trocar `vercelBlobStorage` → `s3Storage` (plugin só incluído quando habilitado):
|
||||||
|
```ts
|
||||||
|
...(mediaStorage.enabled
|
||||||
|
? [
|
||||||
|
s3Storage({
|
||||||
|
collections: { media: true }, // sem disablePayloadAccessControl → proxy
|
||||||
|
bucket: mediaStorage.bucket,
|
||||||
|
config: {
|
||||||
|
credentials: { accessKeyId, secretAccessKey },
|
||||||
|
region: mediaStorage.region, // 'garage' (s3_region do garage.toml)
|
||||||
|
endpoint: mediaStorage.endpoint,
|
||||||
|
forcePathStyle: true, // path-style: endpoint/<bucket>/<key>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
```
|
||||||
|
Sem `acl` (Garage não suporta ACL), sem `clientUploads`/`signedDownloads` (server uploads; bucket privado).
|
||||||
|
- **`src/utilities/mediaStorage.ts`** (novo — módulo puro, unit-testado): `resolveS3StorageEnv(env)` → `{ enabled, bucket, endpoint, region, accessKeyId, secretAccessKey }` ou **throw no boot** com a lista exata das envs faltantes quando a configuração é **parcial** (alguma `S3_*` presente, nem todas). Todas ausentes → `enabled: false` (dev/test usam storage local). Fail-closed: config parcial nunca sobe (nem endpoint default da AWS, nem bucket errado).
|
||||||
|
- **Env (`S3_*`)** — `.env.example`, `.env.test`, `scripts/worktree.mjs` (`s3EnvCopiedLines` all-or-nothing em `scripts/lib/worktree-env.mjs`, unit-testada) no lugar de `copy('BLOB_READ_WRITE_TOKEN')`:
|
||||||
|
- `S3_ENDPOINT` — prod `http://100.119.220.31:3900` (tailnet do homeserver); dev pode usar a mesma ou bucket próprio
|
||||||
|
- `S3_REGION` — `garage` (default; opcional)
|
||||||
|
- `S3_BUCKET` — prod `teqo-media`; dev/test: bucket próprio se quiser S3, senão vazio (storage local)
|
||||||
|
- `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` — key `teqo-key` já existente (read/write no bucket)
|
||||||
|
- **`scripts/seed-posts.mjs`**: remover `blobDel` (`@vercel/blob`) e o pre-delete — o `PutObject` do S3 **sobrescreve** o key determinístico `<slug>.<ext>` nativamente (o problema do `put` sem `allowOverwrite` era específico do Blob); idempotência = lookup por filename no DB (já existe) + overwrite do storage.
|
||||||
|
- **`package.json`**: `remove @payloadcms/storage-vercel-blob` + `@vercel/blob`; `add @payloadcms/storage-s3@3.82.0`. Rodar `pnpm generate:importmap` (desaparece `VercelBlobClientUploadHandler` do importMap).
|
||||||
|
- **`.cursor/cloud-setup.sh`**: remover o sed morto de `YOUR_BLOB_TOKEN_HERE` (o placeholder saiu do `.env.example`).
|
||||||
|
- **Migration:** nenhuma (config-only; `url` é virtual field do upload).
|
||||||
|
- **Access / Consent:** nada muda (`media.read` é público; `canManagePublishedContent` intocado).
|
||||||
|
- **UI:** N/A (Impeccable A).
|
||||||
|
- **AGENTS.md + docs**: atualizar as menções a Blob/media (contexto → resolvido; "Seeding news content" → upload via storage configurado, sem pre-delete; worktree copy `S3_*`). Entrada nova em `docs/changelog/2026-08-18-ops52.md` + `pnpm changelog:build`.
|
||||||
|
|
||||||
|
### Estado real do Garage (verificado 18/08, read-only via API admin)
|
||||||
|
|
||||||
|
- Bucket **`teqo-media` existe** (0 objects, criado 14/08) + key **`teqo-key`** com read/write nele. **Fica privado** (nada de public-read — nem existe no v2). A secret key não está em nenhum `.env` do homeserver — recuperável via `GET /v0/key?id=` e vai para `~/stack/teqo-1313.env` no cutover.
|
||||||
|
- Túnel `garage.solla.dev` → localhost:3900 ativo, mas o **endpoint de uso é o tailnet** `http://100.119.220.31:3900` (tráfego de mídia não passa pelo túnel público).
|
||||||
|
|
||||||
|
## Fases verificáveis
|
||||||
|
|
||||||
|
1. **Core** — módulo `mediaStorage.ts` + unit tests; swap do plugin na config; `pnpm generate:importmap`; `pnpm exec tsc --noEmit` + `pnpm lint` + `pnpm format` + `pnpm exec knip` + `pnpm check:cycles`; `pnpm test` (unit + int). Verificar que dev sem envs sobe e que config parcial lança no boot.
|
||||||
|
2. **Seed + envs** — remover blobDel; `.env.example`/`.env.test`/`s3EnvCopiedLines` (lib + worktree.mjs) + testes; conferir idempotência.
|
||||||
|
3. **Prova de contrato (executada 18/08)** — upload/GET/DELETE de objeto probe via SDK S3 (SigV4) contra o tailnet; depois media criada pelo Payload local com S3 ativo → `media.url` relativa + `GET /api/media/file/<file>` → 200 image/png (streaming do Garage); bucket devolvido a 0 objects e `.env.local` do worktree limpo das credenciais.
|
||||||
|
4. **Gates** — `pnpm gate:fast`; `pnpm build` local.
|
||||||
|
|
||||||
|
## Rabbit holes / Não escopo (engenharia)
|
||||||
|
|
||||||
|
- **Recuperar os 40 arquivos de media** — Issue #10 (OPS52-media), dependente; a forma final dela usa o storage desta entrega.
|
||||||
|
- **Excluir a store do Vercel Blob** — só após cutover estável (documentado, fora do merge).
|
||||||
|
- **Presigned URLs / ACL por objeto / CORS de clientUploads** — bucket privado + server uploads + proxy; nada disso aplica.
|
||||||
|
- **`teqo-media-dev`** — não criar por padrão: dev sem envs usa storage local (isolado por construção). Se um dev quiser S3 local, o `.env.example` documenta bucket próprio.
|
||||||
|
- **Migração de URLs no banco** — as 40 rows relativas são da Issue #10; nenhuma reescrita em massa aqui (e não há URLs Blob publicadas para redirecionar).
|
||||||
|
|
||||||
|
## Riscos e mitigação
|
||||||
|
|
||||||
|
- **Serving do proxy não funcionar com o Garage** → mitigação: fase 3 provou o contrato ponta a ponta (payload.create com S3 ativo → `GET /api/media/file/...` → 200) antes do merge; o `staticHandler` do plugin é o caminho padrão do Payload.
|
||||||
|
- **Garage rejeitar algo do SDK** (ex.: ACL, encoding de key) → sem `acl`; chaves são `<slug>.<ext>` simples; probe real executado na fase 3.
|
||||||
|
- **Boot de produção sem envs completas** → guard no `buildConfig` lança com a lista exata de faltantes; o runbook do deploy (OPS53 + `~/stack/teqo-1313.env`) seta as 5 `S3_*` (4 requeridas + região opcional) antes do rebuild.
|
||||||
|
- **Dev perdendo a habilidade de subir media** → melhoria, não regressão: hoje sem token o upload quebra; com `enabled: false` o dev grava no storage local e o admin local funciona.
|
||||||
|
- **Credenciais de prod em worktree** → `s3EnvCopiedLines` só copia all-or-nothing do main; dev deve manter bucket próprio (contrato do `.env.example`).
|
||||||
|
|
||||||
|
## Aceite de engenharia
|
||||||
|
|
||||||
|
- [ ] Aceite de produto da intenção ainda coberto (storage Garage + seed + fail-closed + ambientes separados)
|
||||||
|
- [ ] Invariantes AGENTS/engineering-standards (nada de access/Consent/transação tocado; naming em inglês; pt-BR só em strings de usuário)
|
||||||
|
- [ ] Testes de domínio: unit do `resolveS3StorageEnv` (ausente/parcial/completa) + unit do `s3EnvCopiedLines` (all-or-nothing) + unit do worktree (envs copiadas)
|
||||||
|
- [ ] Knip limpo (deps mortas removidas), importMap regenerado, docs atualizadas
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
"@payloadcms/next": "3.82.0",
|
"@payloadcms/next": "3.82.0",
|
||||||
"@payloadcms/plugin-import-export": "3.82.0",
|
"@payloadcms/plugin-import-export": "3.82.0",
|
||||||
"@payloadcms/richtext-lexical": "3.82.0",
|
"@payloadcms/richtext-lexical": "3.82.0",
|
||||||
"@payloadcms/storage-vercel-blob": "3.82.0",
|
"@payloadcms/storage-s3": "3.82.0",
|
||||||
"@payloadcms/ui": "3.82.0",
|
"@payloadcms/ui": "3.82.0",
|
||||||
"@simplewebauthn/browser": "^13.3.0",
|
"@simplewebauthn/browser": "^13.3.0",
|
||||||
"@simplewebauthn/server": "^13.3.2",
|
"@simplewebauthn/server": "^13.3.2",
|
||||||
|
|
@ -152,7 +152,6 @@
|
||||||
"@types/topojson-specification": "^1.0.5",
|
"@types/topojson-specification": "^1.0.5",
|
||||||
"@types/web-push": "^3.6.4",
|
"@types/web-push": "^3.6.4",
|
||||||
"@types/yauzl": "^3.4.0",
|
"@types/yauzl": "^3.4.0",
|
||||||
"@vercel/blob": "2.3.1",
|
|
||||||
"@vitejs/plugin-react": "4.5.2",
|
"@vitejs/plugin-react": "4.5.2",
|
||||||
"csv-parse": "^7.0.1",
|
"csv-parse": "^7.0.1",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|
|
||||||
402
pnpm-lock.yaml
402
pnpm-lock.yaml
|
|
@ -44,7 +44,7 @@ importers:
|
||||||
'@payloadcms/richtext-lexical':
|
'@payloadcms/richtext-lexical':
|
||||||
specifier: 3.82.0
|
specifier: 3.82.0
|
||||||
version: 3.82.0(@faceless-ui/modal@3.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@payloadcms/next@3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.29)
|
version: 3.82.0(@faceless-ui/modal@3.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@payloadcms/next@3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.29)
|
||||||
'@payloadcms/storage-vercel-blob':
|
'@payloadcms/storage-s3':
|
||||||
specifier: 3.82.0
|
specifier: 3.82.0
|
||||||
version: 3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@payloadcms/ui':
|
'@payloadcms/ui':
|
||||||
|
|
@ -210,9 +210,6 @@ importers:
|
||||||
'@types/yauzl':
|
'@types/yauzl':
|
||||||
specifier: ^3.4.0
|
specifier: ^3.4.0
|
||||||
version: 3.4.0
|
version: 3.4.0
|
||||||
'@vercel/blob':
|
|
||||||
specifier: 2.3.1
|
|
||||||
version: 2.3.1
|
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: 4.5.2
|
specifier: 4.5.2
|
||||||
version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))
|
version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))
|
||||||
|
|
@ -352,6 +349,88 @@ packages:
|
||||||
'@asamuzakjp/nwsapi@2.3.9':
|
'@asamuzakjp/nwsapi@2.3.9':
|
||||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||||
|
|
||||||
|
'@aws-sdk/checksums@3.1000.28':
|
||||||
|
resolution: {integrity: sha512-VCpnmyHQ1IH49ni3LXnQj7DPr7rmcJmzYeiCkYdCcfgNtkvOj38cdcL9lapBWoItZWFACJPFJlymqC7/gem3Gw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/client-s3@3.1112.0':
|
||||||
|
resolution: {integrity: sha512-E+Z+rIkExcUQbnV9EYfT/fOnveV9B8vIZUzE+wYCVMKgcn/aJJy73FvRf46SGd1NqgT8ESqNlk3Wc/9wE/7/Og==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/core@3.977.8':
|
||||||
|
resolution: {integrity: sha512-7+Kcrkvrk9lM/m7jRhHpT4jCdvzGHsuaSRbF8TdzzkY1mRzp/Ogwf9c7H29k4gGhey0BBWhCWr16+t0J61gwmg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-env@3.972.69':
|
||||||
|
resolution: {integrity: sha512-AreCFzcB4kH2HF9031Ot0jSJr3KXvRg6e8uDeub20JEVdZU3Bv0sTq1plc7VsT3KiqutlzH7l0j50UcCWHUioA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-http@3.972.71':
|
||||||
|
resolution: {integrity: sha512-A8ObcqVmDMnk4F9NozZ7JwmUu9Q4xyBJkmyq1C5U+wNM9ht9J7+EuuyabsLWXZnOoTqFaJuYBYTKf5CTipkEjA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-ini@3.973.14':
|
||||||
|
resolution: {integrity: sha512-7c+Wti2LsERNWMfm7ySz3/6RPopFW3Nmn7s63Xpcq6R/tRuY5hpvkHA2xVgi5ukJbvok9l0IDtVEvqTtg+X7dw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-login@3.972.76':
|
||||||
|
resolution: {integrity: sha512-LVixwOnEJfrrfKHeZjBA8pIMTZjNDq8ak8VpcoWUuCJDrSnBNU8POJksULMgvN089P0MXtQYH2Zs627/MK1K0g==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-node@3.972.80':
|
||||||
|
resolution: {integrity: sha512-bE2qh8ww4iClO1jHsBXdOE8FUgzDbdxbyorNjSCoPSkQd51k3jODItuPZfuwcLHZqDXsH+bI4AMHhqtuyR7mSg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-process@3.972.69':
|
||||||
|
resolution: {integrity: sha512-9kpTNdZTrcqXTfhxM7fgl9Z68ek3Fu5oe3Yf+A/pJGibEqpgZxz2tSY7SinmyCIU2PJ+ygY4FPoBBnLpocMtrQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-sso@3.973.13':
|
||||||
|
resolution: {integrity: sha512-Oc81qauMPzUoTnAS2YKpNwY6sY/LUyQTEeaf6yP197WMxkEBQfcKLR1MFpD7+pNTubXnfkH6gwpji+Gc7iyD2Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-web-identity@3.972.75':
|
||||||
|
resolution: {integrity: sha512-YPN6uoGDgjjjeVFZrcOeCJqmB6zpXoeeNgIjqe+DexJaWqdjVfCCe+VAZwli9Z2h8KhFW8oxkO39emQ1tyz/Mw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/lib-storage@3.1112.0':
|
||||||
|
resolution: {integrity: sha512-IjQd2q6fWjBX7Ipzj3h1gRjd5JVFPRAXGWJdxKGGaQ5XX2aks/O2ibfid/cCZ4IHQO/XQ9azWlzuHCPmjARfQQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/client-s3': ^3.1112.0
|
||||||
|
|
||||||
|
'@aws-sdk/middleware-sdk-s3@3.972.74':
|
||||||
|
resolution: {integrity: sha512-2lzoV2z2QO5KJZYGOCnIZ1WVQgzMECvwuzr1xb034a++8QW4U4eGrmC2u4yg1xvNv4TLL/Uv5DLyuAiw0b9z7Q==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/nested-clients@3.997.43':
|
||||||
|
resolution: {integrity: sha512-bit+VpqWNyi3wHxFoTsTliNXimCSL2r2OeDTm7ZrG+YsTZ2D7ofDJ6r/t9PVBn80i6/v0X2h9Tgw6QP2MAKfPw==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/s3-request-presigner@3.1112.0':
|
||||||
|
resolution: {integrity: sha512-Qmnhl9jpjJrp3Jei6yfnMPdhX/wLxM2kFdUr2V/9PpDhosRUIMfVTNfuq6Mq6WVHfGyLyeksoHt16agzJ50auA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/signature-v4-multi-region@3.996.45':
|
||||||
|
resolution: {integrity: sha512-bBuyztukzXq6plzFGHAWiQt0QXo+HL8b8lX5cFTzkez/74PtS1c0qPFCIVuHkyoT+miH2qOjAcm1/yoro2ESPA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/token-providers@3.1111.0':
|
||||||
|
resolution: {integrity: sha512-JfljgoVtl+s3Qy21n9a7Z48uCQaOXcN74KJ3TEQfPoB293GrXFSt6HSQJF1sTZ8c/5QedEvd3NjJQMO4u9qa5A==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/types@3.974.4':
|
||||||
|
resolution: {integrity: sha512-dSFDNG00MEz0/xl5gxL62giLd1iYyJsTxZ1I1DOj6lC+bbgLB4TRsYClJg3b62dhXT1uATzsTNXPnC+33EJV3A==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/xml-builder@3.972.39':
|
||||||
|
resolution: {integrity: sha512-FTti8DS5MMWXNUWiRwXAJeYS+0GHHiMy0+7XOhcwk63ILHmfS2UFy2z/HNpZCSOJJ3P3dnWY6hfYNW3DF0nXUA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
'@aws/lambda-invoke-store@0.3.0':
|
||||||
|
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@babel/code-frame@7.29.0':
|
'@babel/code-frame@7.29.0':
|
||||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
@ -2102,8 +2181,8 @@ packages:
|
||||||
react: ^19.0.1 || ^19.1.2 || ^19.2.1
|
react: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
|
react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1
|
||||||
|
|
||||||
'@payloadcms/storage-vercel-blob@3.82.0':
|
'@payloadcms/storage-s3@3.82.0':
|
||||||
resolution: {integrity: sha512-Ey8MvW8ZKl54wtCTh6CmoLU/Ov9oRA4NSICzEuxNrmSJfEnyllxG6GW2G0sx/0Ta7eO2xCWeGWTTnK17u5HHKw==}
|
resolution: {integrity: sha512-zIQHvAKudXOq1l9/A+YZl9umiiNfAek/bCWzpaXPF0etJ7f+4a3mGmqzxitt/kpOidCgF3AxX37v7dk3CgeXWQ==}
|
||||||
engines: {node: ^18.20.2 || >=20.9.0}
|
engines: {node: ^18.20.2 || >=20.9.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
payload: 3.82.0
|
payload: 3.82.0
|
||||||
|
|
@ -3031,6 +3110,30 @@ packages:
|
||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@smithy/core@3.33.2':
|
||||||
|
resolution: {integrity: sha512-CUGXpnPkVdjUCbix+83sWLW9VFgQOm44MDOx/ihITJMAnOZKvL8YYIc7DR9pP/tZ8CIRvMiON/TucvygqbHO3w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/credential-provider-imds@4.5.2':
|
||||||
|
resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/fetch-http-handler@5.7.2':
|
||||||
|
resolution: {integrity: sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/node-http-handler@4.11.2':
|
||||||
|
resolution: {integrity: sha512-avwAh9HM3h2lcfjvP3zYIZGf+XVgLQ91wOJ2qoFbNpW1UZeZb33aGlhTZvtkANHfcGhJroRY64525OjfgOg30g==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/signature-v4@5.7.2':
|
||||||
|
resolution: {integrity: sha512-P7Ki6px6OOrxVtx8K7nLmyx4SlXUW/uTKDdMG44UHefmPGSRMBKe2v+TM59WdLcpUIrBrnuCsIqiM2MbsZjmhw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/types@4.17.2':
|
||||||
|
resolution: {integrity: sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0':
|
'@standard-schema/spec@1.1.0':
|
||||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
|
|
@ -3652,10 +3755,6 @@ packages:
|
||||||
vue-router:
|
vue-router:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vercel/blob@2.3.1':
|
|
||||||
resolution: {integrity: sha512-6f9oWC+DbWxIgBLOdqjjn2/REpFrPDB7y5B5HA1ptYkzZaBgL6E34kWrptJvJ7teApJdbAs3I1a5A7z1y8SDHw==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@vercel/oidc@3.2.0':
|
'@vercel/oidc@3.2.0':
|
||||||
resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==}
|
resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
|
|
@ -3891,9 +3990,6 @@ packages:
|
||||||
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
async-retry@1.3.3:
|
|
||||||
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
|
||||||
|
|
||||||
atomic-sleep@1.0.0:
|
atomic-sleep@1.0.0:
|
||||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
@ -3996,6 +4092,9 @@ packages:
|
||||||
body-scroll-lock@4.0.0-beta.0:
|
body-scroll-lock@4.0.0-beta.0:
|
||||||
resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==}
|
resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==}
|
||||||
|
|
||||||
|
bowser@2.14.1:
|
||||||
|
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||||
|
|
||||||
|
|
@ -4024,6 +4123,9 @@ packages:
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
|
buffer@5.6.0:
|
||||||
|
resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
|
||||||
|
|
@ -4885,6 +4987,10 @@ packages:
|
||||||
events-universal@1.0.1:
|
events-universal@1.0.1:
|
||||||
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||||
|
|
||||||
|
events@3.3.0:
|
||||||
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
|
engines: {node: '>=0.8.x'}
|
||||||
|
|
||||||
eventsource-parser@3.0.6:
|
eventsource-parser@3.0.6:
|
||||||
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
@ -5394,10 +5500,6 @@ packages:
|
||||||
is-buffer@1.1.6:
|
is-buffer@1.1.6:
|
||||||
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||||
|
|
||||||
is-buffer@2.0.5:
|
|
||||||
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
is-bun-module@2.0.0:
|
is-bun-module@2.0.0:
|
||||||
resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
|
resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
|
||||||
|
|
||||||
|
|
@ -6902,10 +7004,6 @@ packages:
|
||||||
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
retry@0.13.1:
|
|
||||||
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
|
|
||||||
engines: {node: '>= 4'}
|
|
||||||
|
|
||||||
rettime@0.10.1:
|
rettime@0.10.1:
|
||||||
resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==}
|
resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==}
|
||||||
|
|
||||||
|
|
@ -7158,6 +7256,9 @@ packages:
|
||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
stream-browserify@3.0.0:
|
||||||
|
resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
|
||||||
|
|
||||||
stream-to-array@2.3.0:
|
stream-to-array@2.3.0:
|
||||||
resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==}
|
resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==}
|
||||||
|
|
||||||
|
|
@ -7562,10 +7663,6 @@ packages:
|
||||||
undici-types@6.21.0:
|
undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
undici@6.27.0:
|
|
||||||
resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==}
|
|
||||||
engines: {node: '>=18.17'}
|
|
||||||
|
|
||||||
undici@7.21.0:
|
undici@7.21.0:
|
||||||
resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==}
|
resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
@ -8058,6 +8155,190 @@ snapshots:
|
||||||
|
|
||||||
'@asamuzakjp/nwsapi@2.3.9': {}
|
'@asamuzakjp/nwsapi@2.3.9': {}
|
||||||
|
|
||||||
|
'@aws-sdk/checksums@3.1000.28':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/client-s3@3.1112.0':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/checksums': 3.1000.28
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/credential-provider-node': 3.972.80
|
||||||
|
'@aws-sdk/middleware-sdk-s3': 3.972.74
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.45
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/core@3.977.8':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@aws-sdk/xml-builder': 3.972.39
|
||||||
|
'@aws/lambda-invoke-store': 0.3.0
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/signature-v4': 5.7.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
bowser: 2.14.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-env@3.972.69':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-http@3.972.71':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-ini@3.973.14':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/credential-provider-env': 3.972.69
|
||||||
|
'@aws-sdk/credential-provider-http': 3.972.71
|
||||||
|
'@aws-sdk/credential-provider-login': 3.972.76
|
||||||
|
'@aws-sdk/credential-provider-process': 3.972.69
|
||||||
|
'@aws-sdk/credential-provider-sso': 3.973.13
|
||||||
|
'@aws-sdk/credential-provider-web-identity': 3.972.75
|
||||||
|
'@aws-sdk/nested-clients': 3.997.43
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/credential-provider-imds': 4.5.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-login@3.972.76':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/nested-clients': 3.997.43
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-node@3.972.80':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/credential-provider-env': 3.972.69
|
||||||
|
'@aws-sdk/credential-provider-http': 3.972.71
|
||||||
|
'@aws-sdk/credential-provider-ini': 3.973.14
|
||||||
|
'@aws-sdk/credential-provider-process': 3.972.69
|
||||||
|
'@aws-sdk/credential-provider-sso': 3.973.13
|
||||||
|
'@aws-sdk/credential-provider-web-identity': 3.972.75
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/credential-provider-imds': 4.5.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-process@3.972.69':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-sso@3.973.13':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/nested-clients': 3.997.43
|
||||||
|
'@aws-sdk/token-providers': 3.1111.0
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/credential-provider-web-identity@3.972.75':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/nested-clients': 3.997.43
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/lib-storage@3.1112.0(@aws-sdk/client-s3@3.1112.0)':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.1112.0
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
buffer: 5.6.0
|
||||||
|
events: 3.3.0
|
||||||
|
stream-browserify: 3.0.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/middleware-sdk-s3@3.972.74':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.45
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/nested-clients@3.997.43':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.45
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/fetch-http-handler': 5.7.2
|
||||||
|
'@smithy/node-http-handler': 4.11.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/s3-request-presigner@3.1112.0':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.996.45
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/signature-v4-multi-region@3.996.45':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/signature-v4': 5.7.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/token-providers@3.1111.0':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/core': 3.977.8
|
||||||
|
'@aws-sdk/nested-clients': 3.997.43
|
||||||
|
'@aws-sdk/types': 3.974.4
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/types@3.974.4':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws-sdk/xml-builder@3.972.39':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@aws/lambda-invoke-store@0.3.0': {}
|
||||||
|
|
||||||
'@babel/code-frame@7.29.0':
|
'@babel/code-frame@7.29.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
|
@ -9746,10 +10027,12 @@ snapshots:
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- yjs
|
- yjs
|
||||||
|
|
||||||
'@payloadcms/storage-vercel-blob@3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
'@payloadcms/storage-s3@3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.1112.0
|
||||||
|
'@aws-sdk/lib-storage': 3.1112.0(@aws-sdk/client-s3@3.1112.0)
|
||||||
|
'@aws-sdk/s3-request-presigner': 3.1112.0
|
||||||
'@payloadcms/plugin-cloud-storage': 3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
'@payloadcms/plugin-cloud-storage': 3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@vercel/blob': 2.3.1
|
|
||||||
payload: 3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0)
|
payload: 3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
@ -10768,6 +11051,39 @@ snapshots:
|
||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@sindresorhus/merge-streams@4.0.0': {}
|
||||||
|
|
||||||
|
'@smithy/core@3.33.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/credential-provider-imds@4.5.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/fetch-http-handler@5.7.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/node-http-handler@4.11.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/signature-v4@5.7.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.33.2
|
||||||
|
'@smithy/types': 4.17.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/types@4.17.2':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
'@standard-schema/utils@0.3.0': {}
|
'@standard-schema/utils@0.3.0': {}
|
||||||
|
|
@ -11315,14 +11631,6 @@ snapshots:
|
||||||
next: 15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4)
|
next: 15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4)
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
|
|
||||||
'@vercel/blob@2.3.1':
|
|
||||||
dependencies:
|
|
||||||
async-retry: 1.3.3
|
|
||||||
is-buffer: 2.0.5
|
|
||||||
is-node-process: 1.2.0
|
|
||||||
throttleit: 2.1.0
|
|
||||||
undici: 6.27.0
|
|
||||||
|
|
||||||
'@vercel/oidc@3.2.0': {}
|
'@vercel/oidc@3.2.0': {}
|
||||||
|
|
||||||
'@vercel/speed-insights@1.3.1(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4)':
|
'@vercel/speed-insights@1.3.1(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4)':
|
||||||
|
|
@ -11597,10 +11905,6 @@ snapshots:
|
||||||
|
|
||||||
async-function@1.0.0: {}
|
async-function@1.0.0: {}
|
||||||
|
|
||||||
async-retry@1.3.3:
|
|
||||||
dependencies:
|
|
||||||
retry: 0.13.1
|
|
||||||
|
|
||||||
atomic-sleep@1.0.0: {}
|
atomic-sleep@1.0.0: {}
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
|
|
@ -11688,6 +11992,8 @@ snapshots:
|
||||||
|
|
||||||
body-scroll-lock@4.0.0-beta.0: {}
|
body-scroll-lock@4.0.0-beta.0: {}
|
||||||
|
|
||||||
|
bowser@2.14.1: {}
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
|
|
@ -11719,6 +12025,11 @@ snapshots:
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
buffer-from@1.1.2: {}
|
||||||
|
|
||||||
|
buffer@5.6.0:
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
|
|
@ -12661,6 +12972,8 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
|
|
||||||
|
events@3.3.0: {}
|
||||||
|
|
||||||
eventsource-parser@3.0.6: {}
|
eventsource-parser@3.0.6: {}
|
||||||
|
|
||||||
eventsource-parser@3.1.0: {}
|
eventsource-parser@3.1.0: {}
|
||||||
|
|
@ -13231,8 +13544,6 @@ snapshots:
|
||||||
|
|
||||||
is-buffer@1.1.6: {}
|
is-buffer@1.1.6: {}
|
||||||
|
|
||||||
is-buffer@2.0.5: {}
|
|
||||||
|
|
||||||
is-bun-module@2.0.0:
|
is-bun-module@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.7.4
|
semver: 7.7.4
|
||||||
|
|
@ -15123,8 +15434,6 @@ snapshots:
|
||||||
onetime: 7.0.0
|
onetime: 7.0.0
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
retry@0.13.1: {}
|
|
||||||
|
|
||||||
rettime@0.10.1: {}
|
rettime@0.10.1: {}
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
reusify@1.1.0: {}
|
||||||
|
|
@ -15526,6 +15835,11 @@ snapshots:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
|
stream-browserify@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
stream-to-array@2.3.0:
|
stream-to-array@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
|
|
@ -16000,8 +16314,6 @@ snapshots:
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
undici@6.27.0: {}
|
|
||||||
|
|
||||||
undici@7.21.0: {}
|
undici@7.21.0: {}
|
||||||
|
|
||||||
undici@7.24.4: {}
|
undici@7.24.4: {}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,33 @@ export const testDatabaseForSlot = (slot) => `teqo_wt${slot}_test`
|
||||||
/** DB name charset guard — derived names are fed into `CREATE DATABASE` SQL. */
|
/** DB name charset guard — derived names are fed into `CREATE DATABASE` SQL. */
|
||||||
export const isGeneratedDatabaseName = (name) => /^teqo_wt[0-9]+(_test)?$/.test(name ?? '')
|
export const isGeneratedDatabaseName = (name) => /^teqo_wt[0-9]+(_test)?$/.test(name ?? '')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media storage (OPS52) — the four required `S3_*` keys, mirrored from
|
||||||
|
* `resolveS3StorageEnv` in `src/utilities/mediaStorage.ts` (scripts cannot
|
||||||
|
* import the TS module). `S3_REGION` is optional (default `garage`).
|
||||||
|
*/
|
||||||
|
export const S3_WORKTREE_REQUIRED_KEYS = [
|
||||||
|
'S3_BUCKET',
|
||||||
|
'S3_ENDPOINT',
|
||||||
|
'S3_ACCESS_KEY_ID',
|
||||||
|
'S3_SECRET_ACCESS_KEY',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `S3_*` lines to copy into a worktree env — ALL or NOTHING: a partial set
|
||||||
|
* would abort the worktree's boot (fail-closed `resolveS3StorageEnv`), so a
|
||||||
|
* partial main-env copies nothing and the worktree falls back to local disk
|
||||||
|
* storage. The dev is expected to keep the main env's bucket out of
|
||||||
|
* production (`.env.example` contract).
|
||||||
|
*
|
||||||
|
* @param {Record<string, string | undefined>} env
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
export const s3EnvCopiedLines = (env) =>
|
||||||
|
S3_WORKTREE_REQUIRED_KEYS.every((key) => env[key])
|
||||||
|
? [...S3_WORKTREE_REQUIRED_KEYS, 'S3_REGION'].map((key) => `${key}=${env[key]}`)
|
||||||
|
: []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Final environment for a branch. `takenSlots` = slots already claimed by
|
* Final environment for a branch. `takenSlots` = slots already claimed by
|
||||||
* OTHER live worktrees; bumps +1 until free (bounded — live slot sets are
|
* OTHER live worktrees; bumps +1 until free (bounded — live slot sets are
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
* (against a remote DB, on purpose:) ALLOW_REMOTE_DB=true pnpm db:seed:posts
|
* (against a remote DB, on purpose:) ALLOW_REMOTE_DB=true pnpm db:seed:posts
|
||||||
*/
|
*/
|
||||||
import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext-lexical'
|
import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext-lexical'
|
||||||
import { del as blobDel } from '@vercel/blob'
|
|
||||||
import { JSDOM } from 'jsdom'
|
import { JSDOM } from 'jsdom'
|
||||||
import { getPayload } from 'payload'
|
import { getPayload } from 'payload'
|
||||||
import { dieWithLabel, loadCliEnv } from './lib/cli.mjs'
|
import { dieWithLabel, loadCliEnv } from './lib/cli.mjs'
|
||||||
|
|
@ -381,23 +380,12 @@ async function ensureCoverMedia(payload, { slug, coverUrl, alt }) {
|
||||||
|
|
||||||
const buffer = Buffer.from(await res.arrayBuffer())
|
const buffer = Buffer.from(await res.arrayBuffer())
|
||||||
|
|
||||||
// The Vercel Blob store is SHARED across environments, and the storage plugin
|
// The upload goes through the configured media storage plugin: Garage S3
|
||||||
// (@payloadcms/storage-vercel-blob) calls `put()` without `allowOverwrite` and
|
// when the S3_* envs are set (OPS52), local disk otherwise. The S3 adapter
|
||||||
// exposes no option to enable it. So a blob left behind under this deterministic
|
// overwrites the deterministic key (`<slug>.<ext>`) natively, so no
|
||||||
// key (`<slug>.<ext>`) by an earlier run in another environment would make the
|
// pre-delete is needed — unlike Vercel Blob, whose `put()` refused
|
||||||
// upload throw "This blob already exists". We have no DB media row for it here
|
// overwrites and forced the cleanup dance this script used to do. The
|
||||||
// (checked above), so it's an orphan — delete it first so the upload is
|
// DB-side idempotency (lookup by filename above) is unchanged.
|
||||||
// deterministic and this seed stays re-runnable. `del` is idempotent (no-op
|
|
||||||
// when the blob is absent).
|
|
||||||
const blobToken = process.env.BLOB_READ_WRITE_TOKEN
|
|
||||||
if (blobToken) {
|
|
||||||
try {
|
|
||||||
await blobDel(filename, { token: blobToken })
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`[seed:posts] blob pre-delete skipped for ${filename}: ${err.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const media = await payload.create({
|
const media = await payload.create({
|
||||||
collection: 'media',
|
collection: 'media',
|
||||||
data: { alt: alt || slug },
|
data: { alt: alt || slug },
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ import {
|
||||||
DEV_PORT_BASE,
|
DEV_PORT_BASE,
|
||||||
GENERATED_ENV_MARKER,
|
GENERATED_ENV_MARKER,
|
||||||
isGeneratedDatabaseName,
|
isGeneratedDatabaseName,
|
||||||
|
s3EnvCopiedLines,
|
||||||
worktreeEnvFileContents,
|
worktreeEnvFileContents,
|
||||||
worktreeEnvironment,
|
worktreeEnvironment,
|
||||||
} from './lib/worktree-env.mjs'
|
} from './lib/worktree-env.mjs'
|
||||||
|
|
@ -407,7 +408,8 @@ const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot, purpo
|
||||||
env,
|
env,
|
||||||
payloadSecret,
|
payloadSecret,
|
||||||
copiedLines: [
|
copiedLines: [
|
||||||
...copy('BLOB_READ_WRITE_TOKEN'),
|
// Media storage (OPS52): all-or-nothing (see s3EnvCopiedLines).
|
||||||
|
...s3EnvCopiedLines(mainEnv),
|
||||||
...copy('NEXT_PUBLIC_VAPID_PUBLIC_KEY'),
|
...copy('NEXT_PUBLIC_VAPID_PUBLIC_KEY'),
|
||||||
...copy('VAPID_PUBLIC_KEY'),
|
...copy('VAPID_PUBLIC_KEY'),
|
||||||
...copy('VAPID_PRIVATE_KEY'),
|
...copy('VAPID_PRIVATE_KEY'),
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import { ExportPreview as ExportPreview_cdf7e044479f899a31f804427d568b36 } from
|
||||||
import { ExportSaveButton as ExportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
import { ExportSaveButton as ExportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
||||||
import { ImportPreview as ImportPreview_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
import { ImportPreview as ImportPreview_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
||||||
import { ImportSaveButton as ImportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
import { ImportSaveButton as ImportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
||||||
import { VercelBlobClientUploadHandler as VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e } from '@payloadcms/storage-vercel-blob/client'
|
|
||||||
import { ImportExportProvider as ImportExportProvider_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
import { ImportExportProvider as ImportExportProvider_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc'
|
||||||
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
||||||
|
|
||||||
|
|
@ -75,7 +74,6 @@ export const importMap = {
|
||||||
"@payloadcms/plugin-import-export/rsc#ExportSaveButton": ExportSaveButton_cdf7e044479f899a31f804427d568b36,
|
"@payloadcms/plugin-import-export/rsc#ExportSaveButton": ExportSaveButton_cdf7e044479f899a31f804427d568b36,
|
||||||
"@payloadcms/plugin-import-export/rsc#ImportPreview": ImportPreview_cdf7e044479f899a31f804427d568b36,
|
"@payloadcms/plugin-import-export/rsc#ImportPreview": ImportPreview_cdf7e044479f899a31f804427d568b36,
|
||||||
"@payloadcms/plugin-import-export/rsc#ImportSaveButton": ImportSaveButton_cdf7e044479f899a31f804427d568b36,
|
"@payloadcms/plugin-import-export/rsc#ImportSaveButton": ImportSaveButton_cdf7e044479f899a31f804427d568b36,
|
||||||
"@payloadcms/storage-vercel-blob/client#VercelBlobClientUploadHandler": VercelBlobClientUploadHandler_16c82c5e25f430251a3e3ba57219ff4e,
|
|
||||||
"@payloadcms/plugin-import-export/rsc#ImportExportProvider": ImportExportProvider_cdf7e044479f899a31f804427d568b36,
|
"@payloadcms/plugin-import-export/rsc#ImportExportProvider": ImportExportProvider_cdf7e044479f899a31f804427d568b36,
|
||||||
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import { resendAdapter } from '@payloadcms/email-resend'
|
import { resendAdapter } from '@payloadcms/email-resend'
|
||||||
import { importExportPlugin } from '@payloadcms/plugin-import-export'
|
import { importExportPlugin } from '@payloadcms/plugin-import-export'
|
||||||
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
|
import { s3Storage } from '@payloadcms/storage-s3'
|
||||||
import { pt } from 'payload/i18n/pt'
|
import { pt } from 'payload/i18n/pt'
|
||||||
import { Activity } from './collections/Activity'
|
import { Activity } from './collections/Activity'
|
||||||
import { AllocationDecision } from './collections/AllocationDecision'
|
import { AllocationDecision } from './collections/AllocationDecision'
|
||||||
|
|
@ -46,10 +46,13 @@ import { Metadata } from './globals/Metadata'
|
||||||
import { PrivacyPolicy } from './globals/PrivacyPolicy'
|
import { PrivacyPolicy } from './globals/PrivacyPolicy'
|
||||||
import { SiteSettings } from './globals/SiteSettings'
|
import { SiteSettings } from './globals/SiteSettings'
|
||||||
import { isPayloadAdmin } from './utilities/campaignAccess'
|
import { isPayloadAdmin } from './utilities/campaignAccess'
|
||||||
|
import { resolveS3StorageEnv } from './utilities/mediaStorage'
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
const mediaStorage = resolveS3StorageEnv(process.env)
|
||||||
|
|
||||||
const campaignEmailFromAddress =
|
const campaignEmailFromAddress =
|
||||||
process.env.CAMPAIGN_EMAIL_FROM?.trim() || 'campanha@jorgesolla.com.br'
|
process.env.CAMPAIGN_EMAIL_FROM?.trim() || 'campanha@jorgesolla.com.br'
|
||||||
const campaignEmailFromName = process.env.CAMPAIGN_EMAIL_FROM_NAME?.trim() || 'Campanha Jorge Solla'
|
const campaignEmailFromName = process.env.CAMPAIGN_EMAIL_FROM_NAME?.trim() || 'Campanha Jorge Solla'
|
||||||
|
|
@ -144,12 +147,28 @@ export default buildConfig({
|
||||||
apiKey: process.env.RESEND_API_KEY || '',
|
apiKey: process.env.RESEND_API_KEY || '',
|
||||||
}),
|
}),
|
||||||
plugins: [
|
plugins: [
|
||||||
vercelBlobStorage({
|
// Media storage (OPS52): Garage S3 when the S3_* envs are set, local disk
|
||||||
collections: {
|
// otherwise; a partial S3 config throws at boot (see mediaStorage.ts). The
|
||||||
media: true,
|
// bucket stays private — Payload proxies files through /media/file.
|
||||||
},
|
...(mediaStorage.enabled
|
||||||
token: process.env.BLOB_READ_WRITE_TOKEN,
|
? [
|
||||||
}),
|
s3Storage({
|
||||||
|
collections: {
|
||||||
|
media: true,
|
||||||
|
},
|
||||||
|
bucket: mediaStorage.bucket,
|
||||||
|
config: {
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: mediaStorage.accessKeyId,
|
||||||
|
secretAccessKey: mediaStorage.secretAccessKey,
|
||||||
|
},
|
||||||
|
region: mediaStorage.region,
|
||||||
|
endpoint: mediaStorage.endpoint,
|
||||||
|
forcePathStyle: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
importExportPlugin({
|
importExportPlugin({
|
||||||
collections: [adminCsvExportCollection('signature'), adminCsvExportCollection('contact')],
|
collections: [adminCsvExportCollection('signature'), adminCsvExportCollection('contact')],
|
||||||
overrideExportCollection: withImportExportAdminAccess,
|
overrideExportCollection: withImportExportAdminAccess,
|
||||||
|
|
|
||||||
63
src/utilities/mediaStorage.ts
Normal file
63
src/utilities/mediaStorage.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* Resolves the media storage configuration from the S3_* environment
|
||||||
|
* variables (Garage S3-compatible endpoint).
|
||||||
|
*
|
||||||
|
* Fail-closed contract (OPS52):
|
||||||
|
* - No S3_* var set -> { enabled: false } (dev/test use Payload's local storage)
|
||||||
|
* - Partially set -> throws with the exact list of missing vars (never boots
|
||||||
|
* against the wrong endpoint/bucket, never AWS defaults)
|
||||||
|
* - All set -> { enabled: true, ... } for the s3Storage plugin
|
||||||
|
*
|
||||||
|
* S3_REGION is optional: the Garage server (garage.toml) fixes `s3_region =
|
||||||
|
* "garage"`, which is the only accepted value for signature validation.
|
||||||
|
*
|
||||||
|
* Public URLs are NOT generated: the bucket stays private and Payload proxies
|
||||||
|
* the files through its /media/file route (the plugin's staticHandler streams
|
||||||
|
* the object from Garage), so `media.url` keeps the relative
|
||||||
|
* /media/file/<filename> contract unchanged.
|
||||||
|
*/
|
||||||
|
export const S3_STORAGE_ENV_KEYS = [
|
||||||
|
'S3_BUCKET',
|
||||||
|
'S3_ENDPOINT',
|
||||||
|
'S3_ACCESS_KEY_ID',
|
||||||
|
'S3_SECRET_ACCESS_KEY',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const S3_REGION_DEFAULT = 'garage'
|
||||||
|
|
||||||
|
export type S3StorageEnv =
|
||||||
|
| { enabled: false }
|
||||||
|
| {
|
||||||
|
enabled: true
|
||||||
|
bucket: string
|
||||||
|
endpoint: string
|
||||||
|
region: string
|
||||||
|
accessKeyId: string
|
||||||
|
secretAccessKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveS3StorageEnv(env: Record<string, string | undefined>): S3StorageEnv {
|
||||||
|
const value = (key: string) => env[key]?.trim() || ''
|
||||||
|
|
||||||
|
const present = S3_STORAGE_ENV_KEYS.filter((key) => value(key) !== '')
|
||||||
|
if (present.length === 0) {
|
||||||
|
return { enabled: false }
|
||||||
|
}
|
||||||
|
if (present.length < S3_STORAGE_ENV_KEYS.length) {
|
||||||
|
const missing = S3_STORAGE_ENV_KEYS.filter((key) => !present.includes(key))
|
||||||
|
throw new Error(
|
||||||
|
`Media storage S3 configurado parcialmente: faltam ${missing.join(
|
||||||
|
', ',
|
||||||
|
)}. Configure todas as envs S3_* (bucket, endpoint, credenciais) ou nenhuma (storage local).`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
bucket: value('S3_BUCKET'),
|
||||||
|
endpoint: value('S3_ENDPOINT'),
|
||||||
|
region: value('S3_REGION') || S3_REGION_DEFAULT,
|
||||||
|
accessKeyId: value('S3_ACCESS_KEY_ID'),
|
||||||
|
secretAccessKey: value('S3_SECRET_ACCESS_KEY'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -472,6 +472,9 @@ describe('src/utilities top-level is pinned', () => {
|
||||||
'hookFilledData.ts',
|
'hookFilledData.ts',
|
||||||
'leaderContactsPageData.ts',
|
'leaderContactsPageData.ts',
|
||||||
'loadNamesByIds.ts',
|
'loadNamesByIds.ts',
|
||||||
|
// OPS52 — media storage env resolution; shared by payload.config.ts
|
||||||
|
// (fail-closed S3/Garage switch) and its unit spec.
|
||||||
|
'mediaStorage.ts',
|
||||||
'onda0Provision.ts',
|
'onda0Provision.ts',
|
||||||
'organizationData.ts',
|
'organizationData.ts',
|
||||||
'payloadTransaction.ts',
|
'payloadTransaction.ts',
|
||||||
|
|
|
||||||
56
tests/unit/mediaStorage.unit.spec.ts
Normal file
56
tests/unit/mediaStorage.unit.spec.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// @vitest-environment node
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { resolveS3StorageEnv, S3_STORAGE_ENV_KEYS } from '../../src/utilities/mediaStorage'
|
||||||
|
|
||||||
|
const fullEnv = (): Record<string, string> => ({
|
||||||
|
S3_BUCKET: 'teqo-media',
|
||||||
|
S3_ENDPOINT: 'http://100.119.220.31:3900',
|
||||||
|
S3_ACCESS_KEY_ID: 'AK-test',
|
||||||
|
S3_SECRET_ACCESS_KEY: 'secret',
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolveS3StorageEnv (OPS52)', () => {
|
||||||
|
it('desabilita o storage S3 quando nenhuma env S3_* está presente', () => {
|
||||||
|
expect(resolveS3StorageEnv({})).toEqual({ enabled: false })
|
||||||
|
expect(resolveS3StorageEnv({ DATABASE_URL: 'x', PAYLOAD_SECRET: 'y' })).toEqual({
|
||||||
|
enabled: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('habilita o storage com todas as envs e region default do Garage', () => {
|
||||||
|
expect(resolveS3StorageEnv(fullEnv())).toEqual({
|
||||||
|
enabled: true,
|
||||||
|
bucket: 'teqo-media',
|
||||||
|
endpoint: 'http://100.119.220.31:3900',
|
||||||
|
region: 'garage',
|
||||||
|
accessKeyId: 'AK-test',
|
||||||
|
secretAccessKey: 'secret',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('aceita S3_REGION explícita', () => {
|
||||||
|
const resolved = resolveS3StorageEnv({ ...fullEnv(), S3_REGION: 'garage-dev' })
|
||||||
|
expect(resolved.enabled && resolved.region).toBe('garage-dev')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('lança com a lista exata de envs faltantes na configuração parcial', () => {
|
||||||
|
const partial: Record<string, string> = { ...fullEnv() }
|
||||||
|
delete partial.S3_SECRET_ACCESS_KEY
|
||||||
|
delete partial.S3_ENDPOINT
|
||||||
|
expect(() => resolveS3StorageEnv(partial)).toThrowError(
|
||||||
|
/faltam S3_ENDPOINT, S3_SECRET_ACCESS_KEY/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ignora envs setadas com valor em branco na contagem de parcial', () => {
|
||||||
|
const env = { ...fullEnv(), S3_BUCKET: ' ' }
|
||||||
|
expect(() => resolveS3StorageEnv(env)).toThrowError(/faltam S3_BUCKET/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('lista todas as envs esperadas como obrigatórias (exceto region)', () => {
|
||||||
|
expect(S3_STORAGE_ENV_KEYS).toHaveLength(4)
|
||||||
|
expect(S3_STORAGE_ENV_KEYS).not.toContain('S3_REGION')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
hashSlotOf,
|
hashSlotOf,
|
||||||
isGeneratedDatabaseName,
|
isGeneratedDatabaseName,
|
||||||
numericSlotOfCode,
|
numericSlotOfCode,
|
||||||
|
s3EnvCopiedLines,
|
||||||
testDatabaseForSlot,
|
testDatabaseForSlot,
|
||||||
worktreeEnvFileContents,
|
worktreeEnvFileContents,
|
||||||
worktreeEnvironment,
|
worktreeEnvironment,
|
||||||
|
|
@ -350,6 +351,36 @@ describe('worktreeEnvironment (per-worktree ports and databases)', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('s3EnvCopiedLines (OPS52 media storage, all-or-nothing)', () => {
|
||||||
|
const full = () => ({
|
||||||
|
S3_BUCKET: 'teqo-media',
|
||||||
|
S3_ENDPOINT: 'http://100.119.220.31:3900',
|
||||||
|
S3_ACCESS_KEY_ID: 'AK-test',
|
||||||
|
S3_SECRET_ACCESS_KEY: 'secret',
|
||||||
|
})
|
||||||
|
|
||||||
|
it('copies all five S3_* lines when the four required keys are present', () => {
|
||||||
|
expect(s3EnvCopiedLines({ ...full(), S3_REGION: 'garage' })).toEqual([
|
||||||
|
'S3_BUCKET=teqo-media',
|
||||||
|
'S3_ENDPOINT=http://100.119.220.31:3900',
|
||||||
|
'S3_ACCESS_KEY_ID=AK-test',
|
||||||
|
'S3_SECRET_ACCESS_KEY=secret',
|
||||||
|
'S3_REGION=garage',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('copies nothing when no S3_* key is set (local storage fallback)', () => {
|
||||||
|
expect(s3EnvCopiedLines({ DATABASE_URL: 'x' })).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('copies nothing on a partial set — a partial config would abort the boot', () => {
|
||||||
|
const partial: Record<string, string> = { ...full() }
|
||||||
|
delete partial.S3_SECRET_ACCESS_KEY
|
||||||
|
expect(s3EnvCopiedLines(partial)).toEqual([])
|
||||||
|
expect(s3EnvCopiedLines({ ...full(), S3_BUCKET: '' })).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('worktreeEnvFileContents (dev/test env parity)', () => {
|
describe('worktreeEnvFileContents (dev/test env parity)', () => {
|
||||||
const env = worktreeEnvironment({ branch: 'C15-fullcalendar', code: 'C15' })
|
const env = worktreeEnvironment({ branch: 'C15-fullcalendar', code: 'C15' })
|
||||||
const lines = worktreeEnvFileContents({
|
const lines = worktreeEnvFileContents({
|
||||||
|
|
@ -358,7 +389,11 @@ describe('worktreeEnvFileContents (dev/test env parity)', () => {
|
||||||
generatedBy: 'gerado por pnpm worktree next',
|
generatedBy: 'gerado por pnpm worktree next',
|
||||||
env,
|
env,
|
||||||
payloadSecret: 'secret',
|
payloadSecret: 'secret',
|
||||||
copiedLines: ['BLOB_READ_WRITE_TOKEN=blob', 'VAPID_PRIVATE_KEY=key'],
|
copiedLines: [
|
||||||
|
'S3_BUCKET=teqo-media',
|
||||||
|
'S3_ENDPOINT=http://100.119.220.31:3900',
|
||||||
|
'VAPID_PRIVATE_KEY=key',
|
||||||
|
],
|
||||||
})
|
})
|
||||||
const dev = lines.dev.join('\n')
|
const dev = lines.dev.join('\n')
|
||||||
const test = lines.test.join('\n')
|
const test = lines.test.join('\n')
|
||||||
|
|
@ -386,11 +421,12 @@ describe('worktreeEnvFileContents (dev/test env parity)', () => {
|
||||||
)
|
)
|
||||||
expect(dev).toContain(`PORT=${devPortForSlot(env.slot)}`)
|
expect(dev).toContain(`PORT=${devPortForSlot(env.slot)}`)
|
||||||
expect(dev).toContain('PAYLOAD_SECRET=secret')
|
expect(dev).toContain('PAYLOAD_SECRET=secret')
|
||||||
expect(dev).toContain('BLOB_READ_WRITE_TOKEN=blob')
|
expect(dev).toContain('S3_BUCKET=teqo-media')
|
||||||
|
expect(dev).toContain('S3_ENDPOINT=http://100.119.220.31:3900')
|
||||||
expect(dev).toContain('VAPID_PRIVATE_KEY=key')
|
expect(dev).toContain('VAPID_PRIVATE_KEY=key')
|
||||||
expect(test).not.toContain('PAYLOAD_SECRET')
|
expect(test).not.toContain('PAYLOAD_SECRET')
|
||||||
expect(test).not.toContain('PORT=')
|
expect(test).not.toContain('PORT=')
|
||||||
expect(test).not.toContain('BLOB_READ_WRITE_TOKEN')
|
expect(test).not.toContain('S3_BUCKET')
|
||||||
expect(test).not.toContain('VAPID_PRIVATE_KEY')
|
expect(test).not.toContain('VAPID_PRIVATE_KEY')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue