@leviutima/create-fsd-app
v0.1.0
Published
Scaffold front-end projects with Feature-Sliced Design
Maintainers
Readme
create-fsd-app
CLI que gera projetos front-end já estruturados em Feature-Sliced Design (FSD), com lint de arquitetura configurado desde o primeiro commit — nada de deixar a estrutura de pastas se corromper com o tempo.
Suporta três stacks: Vite + React, Next.js e Expo (React Native).
Por quê
FSD organiza o front-end em camadas (app → pages → widgets → features → entities → shared), onde cada camada só pode importar das camadas abaixo dela, e cada slice só expõe o que quer via uma "public API" (index.ts). Isso é fácil de explicar e fácil de violar sem querer — um import direto num arquivo interno de outro slice, ou um import "pra cima" na hierarquia, passa despercebido em review e corrói a arquitetura aos poucos.
Este gerador cria o projeto já com:
- a estrutura de pastas FSD pronta;
- um slice de exemplo (
entities/user) mostrando o padrão de public API; eslint-plugin-boundariesconfigurado pra bloquear, no lint normal do dia a dia, tanto imports entre camadas na direção errada quanto imports que furam a public API de um slice;steiger(linter oficial do FSD) configurado pra pegar violações estruturais que o ESLint não cobre (slice sem referência, index de camada indevido, etc).
Instalação e uso
Distribuído no npm como @leviutima/create-fsd-app (com escopo — o nome sem escopo create-fsd-app já pertence a outro pacote). Não precisa instalar nada antes, é só rodar via npx:
npx @leviutima/create-fsd-app meu-projetoSem argumento, ele pergunta o nome do projeto e a stack via prompt interativo. Também dá pra passar tudo direto:
npx @leviutima/create-fsd-app meu-projeto --template nextSe preferir instalar global em vez de usar npx toda vez:
npm install -g @leviutima/create-fsd-app
create-fsd-app meu-projetoOpções
| Flag | Valores | Descrição |
|---|---|---|
| [project-name] | texto livre | Nome do projeto e da pasta criada (no diretório atual). Se omitido, é perguntado. |
| -t, --template <name> | vite | next | expo | Qual stack usar. Se omitido, é perguntado. |
Stacks suportadas
| Stack | O que é | Scripts gerados |
|---|---|---|
| Vite | SPA React puro | dev, build, lint, lint:arch, check |
| Next.js | App Router (v14), com src/app fazendo dupla função: roteamento do Next + camada app do FSD | dev, build, start, lint, lint:arch, check |
| Expo | React Native, entrada clássica (App.tsx na raiz + expo/AppEntry.js) | start, android, ios, web, lint, lint:arch, check |
Detalhes por stack:
- Vite:
src/main.tsxmonta<App />no#rootdoindex.html. - Next:
src/app/layout.tsxesrc/app/page.tsxsão os arquivos especiais do App Router;src/app/App.tsxé a camada de composição FSD de fato e está marcada com"use client"porque o slice de exemplo usauseState(o Next trata tudo como Server Component por padrão). - Expo: a UI do slice de exemplo usa
<View>/<Text>em vez de<div>. Otsconfig.jsonrestringe oincludeaApp.tsx+src/**porqueexpo/tsconfig.baseusamoduleResolution: "node", que não resolve os tipos dosteiger(não afeta o app rodando, só otsc --noEmit).
Limitação conhecida: o template Expo foi validado com
npm install, lint,lint:archetsc --noEmit, mas não foi testado num simulador/dispositivo real (esse ambiente não tem Expo Go). Rodenpx expo startnuma máquina com Expo Go pra confirmar visualmente antes de depender dele em produção.
Estrutura gerada
meu-projeto/
├── src/
│ ├── app/ # composição raiz — providers, entry point
│ ├── pages/ # (vazia) telas/rotas
│ ├── widgets/ # (vazia) blocos de UI compostos de features/entities
│ ├── features/ # (vazia) casos de uso interativos
│ ├── entities/
│ │ └── user/ # slice de exemplo
│ │ ├── index.ts # public API — só isso pode ser importado de fora
│ │ ├── model/
│ │ │ ├── store.ts
│ │ │ └── types.ts
│ │ └── ui/
│ │ └── UserCard.tsx
│ └── shared/ # (vazia) código sem regra de negócio (ui kit, libs, utils)
├── eslint.config.js # regras de fronteira entre camadas
├── steiger.config.ts # linter de arquitetura FSD
└── tsconfig.jsonRegra de import entre camadas (de cima pra baixo, cada uma só enxerga as de baixo):
app → pages → widgets → features → entities → sharedshared não importa de nenhuma outra camada. Um slice (ex.: entities/user) só pode ser importado através do seu index.ts — importar direto entities/user/model/store de fora do slice é bloqueado pelo lint.
Rode npm run check (ou lint + lint:arch separados) dentro do projeto gerado pra validar isso.
Como funciona por dentro
create-fsd-app/
├── src/
│ └── index.ts # CLI: parseia args, pergunta nome/stack, copia o template e substitui {{PROJECT_NAME}}
└── templates/
├── vite/ # cada pasta é um projeto completo e auto-contido
├── next/
└── expo/O src/index.ts só faz três coisas: pergunta nome e stack (via commander + prompts), copia templates/<stack> para o diretório de destino, e substitui o placeholder {{PROJECT_NAME}} nos arquivos que precisam dele (package.json, app.json, index.html, src/app/App.tsx — os que existirem em cada template).
Os três templates são independentes entre si (arquivos duplicados, não compartilhados) — é proposital: cada um precisa evoluir conforme a stack correspondente muda, e compartilhar via symlink ou build step adicionaria complexidade desproporcional pra um gerador de escopo pessoal.
Desenvolvendo o CLI
npm run dev # tsup em watch mode
npm run build # build de produção (dist/index.js)Pra testar uma mudança sem publicar:
npm run build
create-fsd-app teste --template vite # usa o link global feito com `npm link`Se o comando create-fsd-app parar de funcionar depois de mexer no bin do package.json, rode npm unlink -g @leviutima/create-fsd-app && npm link de novo pra regenerar os shims globais.
Publicando uma nova versão
A publicação no npm é feita pelo GitHub Actions via Trusted Publishing (OIDC) — não existe token de npm guardado em lugar nenhum, nem manual nem em secret do repositório. Quem autoriza o publish é a combinação repositório + workflow, configurada uma vez em npmjs.com → pacote → Settings → Trusted Publisher.
Pra cortar uma release:
npm version patch # ou minor / major — atualiza package.json e cria a tag
git push --follow-tagsO push da tag vX.Y.Z dispara o workflow .github/workflows/publish.yml, que builda e publica automaticamente. Também dá pra disparar manualmente pela aba Actions do GitHub (workflow_dispatch).
Licença
MIT
