O EmDash é configurado através de dois arquivos: astro.config.mjs para integração e src/live.config.ts para coleções de conteúdo.
Integração Astro
Configure o EmDash como uma integração Astro:
import { defineConfig } from "astro/config";
import emdash, { local, r2, s3 } from "emdash/astro";
import { sqlite, libsql, d1 } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
Opções de integração
database
Obrigatório. Configuração do adaptador de banco de dados.
// SQLite (Node.js)
database: sqlite({ url: "file:./data.db" });
// PostgreSQL
database: postgres({ connectionString: process.env.DATABASE_URL });
// libSQL
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
// Cloudflare D1 (importar de @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
Consulte Opções de banco de dados para obter detalhes.
storage
Obrigatório. Configuração do adaptador de armazenamento de mídia.
// Sistema de arquivos local (desenvolvimento)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// Vinculação R2 (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optional
});
// S3-compatible (any platform) — all fields from S3_* environment variables
storage: s3()
// Or with explicit values
storage: s3({
endpoint: "https://s3.amazonaws.com",
bucket: "my-bucket",
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "us-east-1", // optional, default: "auto"
publicUrl: "https://cdn.example.com", // optional
});
Consulte Opções de armazenamento para obter detalhes.
plugins
Opcional. Array de plugins EmDash.
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
auth
Opcional. Configuração de autenticação.
auth: {
// Configuração de autoinscrição
selfSignup: {
domains: ["example.com"],
defaultRole: 20, // Contributor
},
// Provedores OAuth
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
// Configuração de sessão
session: {
maxAge: 30 * 24 * 60 * 60, // 30 dias
sliding: true, // Redefinir expiração na atividade
},
// OU usar Cloudflare Access (modo exclusivo)
cloudflareAccess: {
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
autoProvision: true,
defaultRole: 30,
syncRoles: false,
roleMapping: {
"Admins": 50,
"Editors": 40,
},
},
}
auth.selfSignup
Permita que os usuários se registrem automaticamente se o domínio de e-mail for permitido.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
domains | string[] | [] | Domínios de e-mail permitidos |
defaultRole | number | 20 | Função para autoinscrições |
selfSignup: {
domains: ["example.com", "acme.org"],
defaultRole: 20, // Contributor
}
auth.oauth
Configure provedores de login OAuth.
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
}
auth.session
Configuração da sessão.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
maxAge | number | 2592000 (30d) | Duração da sessão em segundos |
sliding | boolean | true | Redefinir expiração na atividade |
auth.cloudflareAccess
Use o Cloudflare Access como provedor de autenticação em vez de passkeys.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
teamDomain | string | obrigatório | O domínio da sua equipe de Access |
audience | string | obrigatório | Tag de público do aplicativo (AUD) |
autoProvision | boolean | true | Criar usuários no primeiro login |
defaultRole | number | 30 | Função padrão para novos usuários |
syncRoles | boolean | false | Atualizar função em cada login |
roleMapping | object | — | Mapear grupos IdP para funções |
siteUrl
Opcional. A origem pública voltada para o navegador do site (esquema + host + porta opcional, sem caminho).
Atrás de um proxy reverso com terminação TLS, Astro.url retorna o endereço interno (http://localhost:4321) em vez do público (https://cms.example.com). Isso quebra passkeys, correspondência de origem CSRF, redirecionamentos OAuth, redirecionamentos de login, descoberta MCP, exportações de snapshots, sitemap, robots.txt e dados estruturados JSON-LD. Defina siteUrl para corrigir tudo isso de uma só vez.
A integração valida esse valor no carregamento: ele deve ser uma URL válida com o protocolo http: ou https: e é normalizada para origin (o caminho é removido).
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
Quando siteUrl não está definido na configuração, o EmDash verifica as variáveis de ambiente em ordem: EMDASH_SITE_URL, depois SITE_URL. Isso é útil para implantações em contêiner onde a URL pública é definida em tempo de execução.
Configuração do proxy reverso
O Astro reflete apenas X-Forwarded-* quando o host público é permitido. Configure security.allowedDomains para o nome do host (e esquemas) que seus usuários acessam. Em astro dev, adicione vite.server.allowedHosts correspondente para que o Vite aceite o cabeçalho Host do proxy.
Prefira corrigir allowedDomains (e cabeçalhos encaminhados) primeiro; use siteUrl quando a URL reconstruída ainda divergir da origem do navegador (típico quando o TLS é terminado na frente e a solicitação upstream permanece http://).
Com TLS na frente, vincular o servidor de desenvolvimento ao loopback (astro dev --host 127.0.0.1) geralmente é suficiente: o proxy se conecta localmente enquanto siteUrl corresponde à origem HTTPS pública.
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
security: {
allowedDomains: [
{ hostname: "cms.example.com", protocol: "https" },
{ hostname: "cms.example.com", protocol: "http" },
],
},
vite: {
server: {
allowedHosts: ["cms.example.com"],
},
},
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
}),
],
});
Adaptadores de banco de dados
Importar de emdash/db:
import { sqlite, libsql, postgres, d1 } from "emdash/db";
sqlite(config)
Banco de dados SQLite usando better-sqlite3.
| Opção | Tipo | Descrição |
|---|---|---|
url | string | Caminho do arquivo com prefixo file: |
sqlite({ url: "file:./data.db" });
libsql(config)
Banco de dados libSQL.
| Opção | Tipo | Descrição |
|---|---|---|
url | string | URL do banco de dados |
authToken | string | Token de autenticação (opcional para arquivos locais) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
Banco de dados PostgreSQL com pool de conexões.
| Opção | Tipo | Descrição |
|---|---|---|
connectionString | string | URL de conexão PostgreSQL |
host | string | Host do banco de dados |
port | number | Porta do banco de dados |
database | string | Nome do banco de dados |
user | string | Usuário do banco de dados |
password | string | Senha do banco de dados |
ssl | boolean | Habilitar SSL |
pool.min | number | Tamanho mínimo do pool (padrão: 0) |
pool.max | number | Tamanho máximo do pool (padrão: 10) |
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Banco de dados Cloudflare D1. Importe de @emdash-cms/cloudflare.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
binding | string | — | Nome da vinculação D1 de wrangler.jsonc |
session | string | "disabled" | Modo de replicação de leitura: "disabled", "auto" ou "primary-first" |
bookmarkCookie | string | "__ec_d1_bookmark" | Nome do cookie para marcadores de sessão |
// Básico
d1({ binding: "DB" });
// Com réplicas de leitura
d1({ binding: "DB", session: "auto" });
Quando session é "auto" ou "primary-first", o EmDash usa a API de sessões D1 para rotear consultas de leitura para réplicas próximas. Os usuários autenticados obtêm consistência de leitura-após-escrita baseada em marcadores. Consulte Opções de banco de dados — Réplicas de leitura para obter detalhes.
Adaptadores de armazenamento
Importar de emdash/astro:
import emdash, { local, r2, s3 } from "emdash/astro";
local(config)
Armazenamento de sistema de arquivos local.
| Opção | Tipo | Descrição |
|---|---|---|
directory | string | Caminho do diretório |
baseUrl | string | URL base para servir arquivos |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Vinculação Cloudflare R2.
| Opção | Tipo | Descrição |
|---|---|---|
binding | string | Nome da vinculação R2 |
publicUrl | string | URL público opcional |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
Armazenamento compatível com S3. Todos os campos de configuração são opcionais: qualquer campo omitido em s3({...}) é resolvido a partir da variável de ambiente S3_* correspondente quando o processo Node inicia. Valores explícitos sempre têm precedência.
Pré-requisito: instale @aws-sdk/client-s3 e @aws-sdk/s3-request-presigner no seu projeto. O núcleo do EmDash não inclui o AWS SDK. Consulte Opções de armazenamento → Armazenamento compatível com S3 para detalhes.
| Opção | Tipo | Descrição |
|---|---|---|
endpoint | string | URL do endpoint S3 (S3_ENDPOINT) |
bucket | string | Nome do bucket (S3_BUCKET) |
accessKeyId | string | Chave de acesso (S3_ACCESS_KEY_ID) |
secretAccessKey | string | Chave secreta (S3_SECRET_ACCESS_KEY) |
region | string | Região, padrão "auto" (S3_REGION) |
publicUrl | string | URL CDN opcional (S3_PUBLIC_URL) |
// All fields from S3_* environment variables (Node container deployments)
s3()
// Mix: CDN from config, rest from environment
s3({ publicUrl: "https://cdn.example.com" })
// All explicit (unchanged from before)
s3({
endpoint: "https://xxx.r2.cloudflarestorage.com",
bucket: "media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://cdn.example.com",
})
A resolução de variáveis de ambiente em tempo de execução é uma funcionalidade exclusiva do Node. No Cloudflare Workers, segredos e variáveis são expostos através do parâmetro env do manipulador fetch, não através de process.env, então as variáveis de ambiente S3_* não são lidas. Implantações no Workers devem usar o adaptador r2(config) ou passar valores explícitos para s3({...}). Consulte Opções de armazenamento para detalhes.
Coleções ao vivo
Configure o carregador EmDash em src/live.config.ts:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
Opções do carregador
A função emdashLoader() aceita configuração opcional:
emdashLoader({
// Atualmente sem opções - reservado para uso futuro
});
Variáveis de ambiente
O EmDash respeita estas variáveis de ambiente:
| Variável | Descrição |
|---|---|
EMDASH_SITE_URL | Origem pública voltada para o navegador (recorre a SITE_URL) |
EMDASH_DATABASE_URL | Substituir URL do banco de dados |
EMDASH_AUTH_SECRET | Segredo para autenticação de passkey |
EMDASH_PREVIEW_SECRET | Segredo para geração de token de visualização |
EMDASH_URL | URL EmDash remoto para sincronização de esquema |
Gere um segredo de autenticação com:
npx emdash auth secret
Configuração package.json
Configuração opcional em package.json:
{
"emdash": {
"label": "My Blog Template",
"description": "A clean, minimal blog template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev",
"preview": "https://emdash-blog.pages.dev"
}
}
| Opção | Descrição |
|---|---|
label | Nome do modelo para exibição |
description | Descrição do modelo |
seed | Caminho para o arquivo JSON seed |
url | URL remoto para sincronização de esquema |
preview | URL do site de demonstração para visualização do modelo |
Configuração TypeScript
EmDash gera tipos em .emdash/types.ts. Adicione ao seu tsconfig.json:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Gere tipos com:
npx emdash types