Scaffold — create-tempest-app
create-tempest-app é a CLI oficial de scaffolding da Tempest. Com um comando você cria um app React 19 + Vite + TypeScript já pré-cabeado com o tempest-react-sdk: providers, roteamento, store de autenticação e cliente HTTP saem prontos pra rodar. A CLI não é um pacote separado — ela vem dentro do próprio tempest-react-sdk como o bin do pacote (create-tempest-app), junto com um template/ embutido no tarball.
"bin": { "create-tempest-app": "./bin/create-tempest-app.mjs" }
Esta página é um tutorial: vamos do comando vazio até o app rodando no navegador, e depois passeamos por cada arquivo gerado pra entender qual recurso do SDK ele demonstra. 🚀
Versionada junto com o SDK
Como a CLI mora dentro do tempest-react-sdk, ela é versionada junto com o SDK. Fixar uma versão é fixar a versão do SDK: npx -p tempest-react-sdk@0.5.1 create-tempest-app …. E o app gerado já nasce com a dependência tempest-react-sdk carimbada na mesma versão que o produziu — nada de número hardcoded que desatualiza.
Crie seu primeiro app
O caminho recomendado é criar a pasta você mesmo e scaffoldar dentro dela com .:
mkdir my-app
cd my-app
npx -p tempest-react-sdk create-tempest-app .
npm install
cp .env.example .env
npm run dev # http://127.0.0.1:5173
Abra http://127.0.0.1:5173 — o app já está no ar com providers, rotas e store funcionando.
O que cada pedaço do comando faz
| Pedaço | O que faz |
|---|---|
npx |
Baixa e executa um binário sem instalar nada global — descarta o download depois. |
-p tempest-react-sdk |
Diz de qual pacote vem o binário. Necessário porque a CLI mora dentro do SDK, com nome diferente do nome do pacote. |
create-tempest-app |
O nome do bin dentro daquele pacote — é o que roda de fato. |
. |
O destino: o diretório atual (modo recomendado). Um nome no lugar do . cria a pasta e exige que ela esteja vazia. |
Prefira . a create-tempest-app my-app
As duas formas funcionam, mas . é melhor no dia a dia:
- Você controla a pasta e o nome — o
namedopackage.jsonsai do nome do diretório atual, sem risco de acabar commy-app/my-apppor já estar dentro da pasta. - Convive com o que já existe —
git init,README.md,LICENSEe afins são preservados (a CLI lista o que pulou). O modo com nome aborta se a pasta não estiver vazia. - Um comando só pra pasta nova e pra projeto existente.
Rodar sem argumento = .
npx -p tempest-react-sdk create-tempest-app (sem nada depois) faz exatamente o mesmo que passar .: scaffold no diretório atual. A CLI não pergunta nome de projeto.
npm create tempest-app não funciona
Não existe pacote create-tempest-app publicado no npm — a CLI é o bin do tempest-react-sdk, então npm create tempest-app falha com 404. Use npx -p tempest-react-sdk create-tempest-app .; dentro de um projeto que já tem o SDK instalado, o -p é dispensável.
No modo com nome, a pasta precisa estar vazia
Em create-tempest-app my-app, o diretório alvo não pode existir ou precisa estar vazio — isso evita sobrescrever um projeto seu por acidente. Se a pasta já tem arquivos, a CLI sugere usar . para mesclar no diretório atual em vez de abortar (veja a próxima seção).
Os dois modos, lado a lado
| Você digita | Destino | Se a pasta tem arquivos | Nome do projeto |
|---|---|---|---|
create-tempest-app . |
diretório atual | preserva os seus e reporta o que foi pulado | nome da pasta atual |
create-tempest-app (só) |
diretório atual | idem | nome da pasta atual |
create-tempest-app my-app |
./my-app |
aborta se existir e não estiver vazia | my-app |
Scaffold dentro de um projeto existente
Se você já tem um projeto que depende do SDK, dá pra gerar o src/ + configs no diretório atual, sem criar uma pasta nova:
npm install tempest-react-sdk
npx create-tempest-app .
Aqui o npx create-tempest-app resolve o bin a partir do tempest-react-sdk que você acabou de instalar — não precisa do -p. Rodar sem argumento se comporta igual a ..
Nesse modo "diretório atual":
- Arquivos existentes são preservados — a CLI pula cada um que já existe e reporta o que foi pulado, sem sobrescrever nada seu.
- Um
package.jsonexistente tem os scripts e deps da Tempest mesclados: seuname/versione os scripts/deps que já estavam lá são mantidos, e otempest-react-sdké fixado na própria versão do SDK que está rodando o scaffold.
O .env
O .env.example declara VITE_API_URL, a base usada pelo cliente HTTP em src/lib/api.ts. Copie pra .env e ajuste pra apontar pro seu backend.
Scripts disponíveis
O package.json gerado vem com estes scripts:
| Script | O que faz |
|---|---|
npm run dev |
vite — dev server em 127.0.0.1:5173 |
npm run build |
tsc --noEmit && vite build — checa tipos e empacota |
npm run preview |
vite preview — serve o build de produção |
npm run typecheck |
tsc --noEmit — só checagem de tipos |
npm run lint |
eslint . — ESLint 9 flat config (react-hooks + refresh) |
npm run lint:fix |
eslint . --fix — autocorrige o que dá |
Tour pelo que foi gerado
O projeto gerado é enxuto de propósito: cada arquivo existe pra demonstrar um recurso do SDK que você vai reaproveitar. Esta é a estrutura completa:
my-app/
├── index.html
├── package.json # deps: react, react-dom, tempest-react-sdk; devDeps: vite, @vitejs/plugin-react, typescript, @types/*
├── tsconfig.json # alias @ -> ./src em "paths"
├── vite.config.ts # export default createViteConfig()
├── .env.example # VITE_API_URL
├── .gitignore
└── src/
├── main.tsx # createRoot + "tempest-react-sdk/styles.css" + <App/>
├── App.tsx # <AppProviders> envolvendo <AppRouter routes fallback/>
├── routes.tsx # defineRoutes([...]) com index, login e dashboard lazy + protegido
├── layouts/RootLayout.tsx # nav (Link) + <Outlet/>, lê useAuth.use.isAuthenticated()
├── pages/Home.tsx
├── pages/Login.tsx # fakeia uma sessão via useAuth.use.setSession() e navigate("/dashboard")
├── pages/Dashboard.tsx # export default (lazy), rota protegida
├── stores/auth.ts # createSelectors(createAuthStore<User>({ name: "app-auth" }))
└── lib/api.ts # createApiClient({ baseURL, getToken, onUnauthorized }) + createQueryKeys
Cada arquivo → o recurso do SDK que ele mostra
| Arquivo | Recurso do SDK demonstrado |
|---|---|
vite.config.ts |
createViteConfig — config Vite pronta pro SDK |
src/App.tsx |
AppProviders + AppRouter — providers e roteamento |
src/routes.tsx |
defineRoutes com rota lazy + guard de autenticação |
src/stores/auth.ts |
createAuthStore + createSelectors — store de auth tipada |
src/lib/api.ts |
createApiClient + createQueryKeys — cliente HTTP + cache |
Vamos olhar os três mais importantes.
vite.config.ts → createViteConfig
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig();
Uma linha. O createViteConfig já liga o plugin de React, o alias @ -> ./src e os defaults que o SDK espera. Veja a página Vite Config pra customizar.
src/App.tsx → AppProviders + AppRouter
import { AppProviders, AppRouter } from "tempest-react-sdk";
import { routes } from "@/routes";
export function App() {
return (
<AppProviders errorBoundary={{ fallback: <p>Something went wrong.</p> }}>
<AppRouter routes={routes} fallback={<p>Loading…</p>} />
</AppProviders>
);
}
AppProviders monta numa tacada só o React Query, o error boundary, o tema e o roteador. AppRouter consome o array de rotas e renderiza o fallback enquanto rotas lazy carregam. Detalhes em App Providers.
src/stores/auth.ts → createAuthStore + createSelectors
import { createAuthStore, createSelectors } from "tempest-react-sdk";
export interface User {
id: string;
name: string;
email: string;
}
export const useAuth = createSelectors(createAuthStore<User>({ name: "app-auth" }));
createAuthStore<User> cria uma store Zustand de autenticação persistida (name: "app-auth" é a chave de storage). createSelectors te dá acesso atômico via useAuth.use.<campo>() — é assim que RootLayout.tsx lê useAuth.use.isAuthenticated() e Login.tsx chama useAuth.use.setSession(). Mais padrões em State.
O resto se explica sozinho
routes.tsx usa defineRoutes([...]) com uma rota index, uma de login e um dashboard que é ao mesmo tempo lazy e protegido por guard. lib/api.ts instancia createApiClient com baseURL/getToken/onUnauthorized e exporta createQueryKeys pra você organizar as chaves de cache.
Modo PWA (--pwa)
Quer que o app já nasça instalável (ícone na home screen), capaz de emitir notificações web push e funcionar offline (app shell + cache)? Passe a flag --pwa:
npx -p tempest-react-sdk create-tempest-app . --pwa
A flag funciona nos dois modos (pasta nova e ./merge). Ela sobrepõe o template PWA por cima do base: tudo do app normal continua igual, mais alguns arquivos novos e alguns sobrescritos.
Sem vite-plugin-pwa, sem Workbox
Instalação, push e o cache offline são montados com os próprios helpers do SDK (tempest-react-sdk/sw + tempest-react-sdk/vite) e empacotados por um build dedicado. Nada de dependência extra de PWA — o app PWA usa exatamente as mesmas deps do app base. (Comparação completa vs vite-plugin-pwa mais abaixo.)
O que a flag adiciona
my-app/
├── index.html # (sobrescrito) link do manifest + theme-color + metas apple
├── vite.config.ts # (sobrescrito) createViteConfig + tempestPwaIcons + Manifest + DevSw
├── vite.sw.config.ts # build dedicado que empacota src/sw.ts -> dist/sw.js
├── public/
│ ├── manifest.webmanifest # metadados de instalação (aponta pros PNGs gerados)
│ └── icon.svg # ícone-fonte (troque pelo seu — os PNGs saem daqui)
└── src/
├── sw.ts # service worker: push + notificationclick + skip-waiting + cache
├── main.tsx # (sobrescrito) registra /sw.js em dev e produção
├── vite-env.d.ts # (sobrescrito) tipa VITE_VAPID_PUBLIC_KEY
└── pages/Dashboard.tsx # (sobrescrito) botão Instalar + toggle de notificações
O package.json também é ajustado: o script build passa a empacotar o SW (tsc --noEmit && vite build && npm run build:sw) e ganha um build:sw; sharp entra como devDependency (gera os ícones). O build emite dist/precache-manifest.json (lista de assets pro cache offline) via tempestPwaManifest() e o set de ícones PNG (dist/icons/*.png + apple-touch-icon.png) via tempestPwaIcons().
Em merge, seus arquivos são preservados
No modo . (mesclar em projeto existente), a CLI nunca sobrescreve um arquivo seu — só os que ela mesma acabou de gerar. Se você já tinha um index.html, ele é pulado e reportado, e a parte PWA dele fica por sua conta.
As cinco peças
1. Instalação → useBeforeInstallPrompt
O index.html linka o manifest.webmanifest, e o Dashboard.tsx mostra um botão Instalar só quando o navegador oferece o prompt:
const install = useBeforeInstallPrompt();
// ...
{
install.installable && <Button onClick={() => void install.prompt()}>Install app</Button>;
}
2. Service worker → tempest-react-sdk/sw
O src/sw.ts é só cola em cima dos helpers do SDK:
/// <reference lib="webworker" />
import {
installNotificationClickHandler,
installPushHandler,
installSkipWaitingListener,
} from "tempest-react-sdk/sw";
installPushHandler({ defaultTitle: "Notificação", defaultIcon: "/icon.svg" });
installNotificationClickHandler();
installSkipWaitingListener();
O vite.sw.config.ts empacota esse arquivo (e os helpers que ele importa) num service worker clássico em dist/sw.js, e o main.tsx registra ele via registerServiceWorker. Em dev, o plugin tempestPwaDevSw() compila o sw.ts na hora e serve em /sw.js — então push e cache funcionam também no npm run dev (sem ele, o SW só existiria no build). A compilação passa por um contexto incremental do esbuild, criado na primeira requisição: o browser rebusca o script do worker a cada navegação e nas checagens de update dele, então bundle a frio por requisição seria bundle a frio o dia inteiro. Veja os detalhes dos helpers em Web Push.
3. Web push → usePushSubscription
O Dashboard.tsx liga o toggle de notificações ao hook, lendo a chave VAPID do .env:
const push = usePushSubscription({
vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY ?? "",
onSubscribe: async (subscription) => {
// mande a subscription pro seu backend entregar os pushes
await api.post("/webpush/subscribe", { body: subscription });
},
onUnsubscribe: async () => {
await api.delete("/webpush/my");
},
});
4. Offline → installPrecache + installRuntimeCache
O vite.config.ts adiciona o plugin tempestPwaManifest(), que emite um dist/precache-manifest.json com todos os assets do build (o equivalente sem-dependência ao __WB_MANIFEST do Workbox). No sw.ts, dois helpers consomem isso:
import { installPrecache, installRuntimeCache } from "tempest-react-sdk/sw";
// Rotas específicas PRIMEIRO (ganham do catch-all do precache):
installRuntimeCache([
{
match: (url) => url.pathname.startsWith("/api/"),
strategy: "network-first", // ou "cache-first" / "stale-while-revalidate"
cacheName: "api",
networkTimeoutSeconds: 5,
maxEntries: 50,
maxAgeSeconds: 60 * 5,
},
]);
// App shell por último — abre offline:
installPrecache({ navigateFallback: "/index.html", navigateFallbackDenylist: [/^\/api\//] });
installPrecachecacheia o app shell noinstall, serve assets cache-first, e devolve onavigateFallback(SPA) quando a navegação acontece offline. Versiona o cache peloversiondo manifest e limpa versões antigas noactivate.installRuntimeCacheaplica estratégias por rota (cache-first / network-first / stale-while-revalidate) commaxEntriesemaxAgeSeconds.
5. Ícones → tempestPwaIcons
O vite.config.ts adiciona o plugin tempestPwaIcons({ source: "public/icon.svg" }), que no build rasteriza um único SVG-fonte no set completo de ícones — o equivalente sem-dependência ao @vite-pwa/assets-generator:
import { tempestPwaIcons } from "tempest-react-sdk/vite";
tempestPwaIcons({ source: "public/icon.svg" });
// emite: dist/icons/icon-192.png, icon-512.png, maskable-512.png, dist/apple-touch-icon.png
A rasterização usa sharp (já incluído como devDependency do template). O manifest.webmanifest aponta pros PNGs gerados, e o tempestPwaManifest() os inclui no precache automaticamente. Trocar o ícone do app = trocar o public/icon.svg.
sharp é opcional
O plugin importa sharp de forma preguiçosa: se ele não estiver instalado, o build não falha — só emite um aviso e pula a geração. O template já traz sharp como devDep, então funciona out-of-the-box.
Push e offline só valem num build de produção
A geração de ícones acontece em tempo de build, e o app shell só é precacheado depois de npm run build. No npm run dev o tempestPwaDevSw() serve o SW (push + runtime cache funcionam), mas o precache offline e os PNGs só existem no build. Pra testar instalação + offline completos, rode npm run build && npm run preview (e no DevTools › Network marque Offline pra ver o app shell servir).
--pwa vs vite-plugin-pwa
O --pwa agora cobre o mesmo terreno do vite-plugin-pwa para o caso comum, sem dependência de runtime nova:
| Recurso | vite-plugin-pwa (Workbox) |
--pwa (SDK) |
|---|---|---|
| Manifest + instalável | ✅ | ✅ |
| Install prompt | manual | ✅ useBeforeInstallPrompt |
Web push + notificationclick |
você escreve | ✅ helpers do SDK |
| Update flow (skip-waiting) | ✅ | ✅ registerServiceWorker |
| Precache do app shell | ✅ (__WB_MANIFEST) |
✅ tempestPwaManifest + installPrecache |
| Runtime caching (cache/network/SWR) | ✅ | ✅ installRuntimeCache |
navigateFallback (SPA offline) |
✅ | ✅ |
| Limpeza de caches antigos | ✅ | ✅ (versão no activate) |
| Geração automática de ícones | ✅ (sharp) | ✅ tempestPwaIcons (sharp, opcional) |
| SW em dev | ✅ (devOptions) |
✅ tempestPwaDevSw (esbuild) |
| Background Sync | ✅ (BackgroundSyncPlugin) |
✅ installBackgroundSync |
| Range requests (mídia) | ✅ (RangeRequestsPlugin) |
✅ installRuntimeCache({ rangeRequests }) |
| Splash screens (Apple) | ✅ (assets generator) | ✅ tempestPwaIcons({ appleSplash }) |
Cobertura total do caso comum. Pra extremos muito específicos (range requests parciais de precache, estratégias Workbox exóticas), vite-plugin-pwa ainda existe e pode ser passado via plugins: [...] no createViteConfig.
Recursos avançados
Background Sync — mutações offline que reenviam sozinhas
installBackgroundSync enfileira POST/PUT/PATCH/DELETE que falham offline (IndexedDB) e os reenvia quando a conexão volta — via Background Sync API onde existe, e oportunisticamente (no próximo request) onde não:
import { installBackgroundSync } from "tempest-react-sdk/sw";
installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
O fetch original ainda rejeita no momento (seu app mostra estado offline), mas a requisição é replayada depois. maxRetentionMinutes descarta entradas velhas; respostas 4xx são dropadas (erro do cliente não se conserta sozinho).
Range requests — seek de áudio/vídeo offline
Marque uma rota com rangeRequests: true pra servir 206 Partial Content fatiando o recurso cacheado — sem isso, mídia não dá seek offline:
installRuntimeCache([
{
match: (url) => /\.(mp3|mp4|webm)$/.test(url.pathname),
strategy: "cache-first",
cacheName: "media",
rangeRequests: true,
},
]);
O helper createPartialResponse(request, response) também é exportado pra uso manual.
Splash screens (Apple) — telas de abertura no iOS
tempestPwaIcons({ appleSplash: true }) gera as launch images por device (iPhone/iPad, portrait) e injeta os <link rel="apple-touch-startup-image" media=...> no index.html. Passe um array pra customizar os tamanhos:
tempestPwaIcons({
source: "public/icon.svg",
appleSplash: [{ width: 390, height: 844, ratio: 3 }],
});
Recap do modo PWA
--pwa te entrega manifest + service worker + push + cache offline + ícones gerados + SW em dev + background sync + range requests + splash screens, já cabeados sobre o mesmo app base, usando tempest-react-sdk/sw (installPushHandler, installPrecache, installRuntimeCache, installBackgroundSync, createPartialResponse), tempest-react-sdk/vite (tempestPwaManifest, tempestPwaIcons, tempestPwaDevSw), usePushSubscription e useBeforeInstallPrompt — sem vite-plugin-pwa. Gere o VAPID no backend, preencha VITE_VAPID_PUBLIC_KEY no .env, troque o public/icon.svg, e teste com npm run build && npm run preview. 🚀
Próximos passos
Com o app rodando, aqui está como crescer a partir dele:
1. Adicione uma página
Crie src/pages/Sobre.tsx e registre a rota em src/routes.tsx:
import { defineRoutes } from "tempest-react-sdk";
export const routes = defineRoutes([
// ...rotas existentes
{ path: "/sobre", element: <Sobre /> },
]);
2. Adicione uma store
Pra estado que não é de autenticação, use createStore do SDK:
import { createStore } from "tempest-react-sdk";
export const useCounter = createStore<{ count: number; inc: () => void }>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
3. Busque dados com React Query + queryKeys + api
Combine o cliente HTTP, as query keys e o useQuery (já disponível pelo AppProviders):
import { useQuery } from "@tanstack/react-query";
import { api, queryKeys } from "@/lib/api";
export function UserList() {
const { data, isLoading } = useQuery({
queryKey: queryKeys.users.all,
queryFn: () => api.get("/users"),
});
if (isLoading) return <p>Carregando…</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
Recap
- A CLI é o
bindo própriotempest-react-sdk(create-tempest-app), com umtemplate/embutido no tarball — versionada junto com o SDK, não um pacote separado. ✅ - Caminho recomendado:
mkdir my-app && cd my-app && npx -p tempest-react-sdk create-tempest-app .— o.(igual a rodar sem argumento) gera no diretório atual, preserva arquivos existentes e tira o nome do projeto da pasta. Use@Xno-ppra fixar a versão do SDK. - Modo com nome:
npx -p tempest-react-sdk create-tempest-app my-appcria a pasta, mas aborta se ela já existir e não estiver vazia. A CLI não pergunta nome de projeto em nenhum modo. - Projeto existente:
npm install tempest-react-sdkenpx create-tempest-app .(sem-p, obinvem donode_modules) — opackage.jsontem scripts/deps mesclados (tempest-react-sdkfixado na versão do SDK). npm install && cp .env.example .env && npm run devte leva a http://127.0.0.1:5173 com providers, rotas e auth funcionando.- Cada arquivo gerado demonstra um recurso:
createViteConfig,AppProviders+AppRouter,defineRoutes(lazy + guard),createAuthStore+createSelectors,createApiClient+createQueryKeys. - Pra crescer: adicione páginas em
pages/+ entradas emroutes.tsx, crie stores comcreateStore, e busque dados comuseQuery+queryKeys+api.