create-sdd-ai-stack 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +164 -0
- package/APP-STACK.md +31 -0
- package/APP.md +46 -0
- package/ARCHITECTURE.md +115 -0
- package/DESIGN.md +219 -0
- package/LICENSE +21 -0
- package/NEXT.md +345 -0
- package/NODE.md +107 -0
- package/REACT.md +92 -0
- package/README.md +260 -0
- package/SKILLS/check-docs/SKILL.md +24 -0
- package/SKILLS/check-docs/check-docs.mjs +56 -0
- package/SKILLS/create-feature/SKILL.md +40 -0
- package/SKILLS/create-feature/create-feature.mjs +128 -0
- package/SKILLS/create-feature.sh +112 -0
- package/SKILLS/install-submodule/SKILL.md +42 -0
- package/SKILLS/install-submodule/install-submodule.mjs +119 -0
- package/bin/create-sdd-ai-stack.mjs +93 -0
- package/docs/CHANGELOG.md +209 -0
- package/docs/PLANNING.md +68 -0
- package/docs/PRODUCT.md +76 -0
- package/docs/RELEASE.md +332 -0
- package/lib/check-links.mjs +42 -0
- package/lib/constants.mjs +60 -0
- package/lib/scaffold.mjs +253 -0
- package/package.json +80 -0
- package/specs/BACKLOG.md +24 -0
- package/specs/PLAN.md +57 -0
- package/specs/ROADMAP.md +35 -0
- package/specs/history/phases/phase-0-bootstrap.md +67 -0
- package/specs/tasks/TASK_TEMPLATE.md +46 -0
- package/src/cli.mjs +119 -0
- package/stacks/README.md +33 -0
- package/stacks/ai.md +52 -0
- package/stacks/ci.md +59 -0
- package/stacks/database.md +56 -0
- package/stacks/git.md +51 -0
- package/stacks/shadcn.md +52 -0
- package/stacks/tailwind.md +65 -0
- package/stacks/testing.md +74 -0
- package/stacks/typescript.md +58 -0
- package/template/next/.env.example +4 -0
- package/template/next/README.md +47 -0
- package/template/next/biome.json +36 -0
- package/template/next/gitignore +32 -0
- package/template/next/next.config.ts +12 -0
- package/template/next/package.json +47 -0
- package/template/next/playwright.config.ts +21 -0
- package/template/next/postcss.config.mjs +7 -0
- package/template/next/src/app/(app)/app/page.tsx +18 -0
- package/template/next/src/app/(app)/error.tsx +30 -0
- package/template/next/src/app/(app)/layout.tsx +25 -0
- package/template/next/src/app/(marketing)/page.tsx +73 -0
- package/template/next/src/app/globals.css +437 -0
- package/template/next/src/app/layout.tsx +41 -0
- package/template/next/src/app/not-found.tsx +11 -0
- package/template/next/src/features/example/actions.ts +43 -0
- package/template/next/src/features/example/application/create-example.usecase.ts +26 -0
- package/template/next/src/features/example/container.ts +16 -0
- package/template/next/src/features/example/domain/IExampleRepository.ts +11 -0
- package/template/next/src/features/example/domain/example.schema.ts +18 -0
- package/template/next/src/features/example/infrastructure/example.repository.ts +26 -0
- package/template/next/src/features/example/ui/create-example-form.tsx +56 -0
- package/template/next/src/proxy.ts +23 -0
- package/template/next/src/shared/lib/cn.ts +6 -0
- package/template/next/src/shared/server/auth.ts +32 -0
- package/template/next/src/shared/server/env.ts +14 -0
- package/template/next/src/shared/ui/index.ts +3 -0
- package/template/next/tests/e2e/routes.spec.ts +34 -0
- package/template/next/tests/unit/create-example.test.ts +55 -0
- package/template/next/tsconfig.json +37 -0
- package/template/next/vitest.config.ts +19 -0
package/docs/RELEASE.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# π RELEASE β publicar no npm
|
|
2
|
+
|
|
3
|
+
> **Fluxo ΓΊnico:** vocΓͺ versiona e cria a tag localmente; o **GitHub Actions publica**.
|
|
4
|
+
> NΓ£o existe `npm publish` manual neste projeto β Γ© proposital, para nΓ£o ter dois caminhos.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
npm run version:minor β 0.1.17 β 0.2.0 (commita + cria tag v0.2.0)
|
|
8
|
+
git push origin main --tags β dispara o workflow Release β publica no npm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 0. Como versionar (estamos em `0.x`)
|
|
14
|
+
|
|
15
|
+
O projeto estΓ‘ em **`0.1.x`** de propΓ³sito: as regras e o template ainda vΓ£o mudar com base
|
|
16
|
+
no uso real. Leitura das versΓ΅es:
|
|
17
|
+
|
|
18
|
+
| MudanΓ§a | Bump | Exemplo | Version bump |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| CorreΓ§Γ£o de bug, ajuste de doc, nova regra pontual | **patch** | `0.1.17 β 0.1.18` | `npm run version:patch` |
|
|
21
|
+
| Regra nova, feature nova no template, Breaking em config | **minor** | `0.1.17 β 0.2.0` | `npm run version:minor` |
|
|
22
|
+
| Reescritura de regra estrutural (ex.: Next 16 β 17) | **major** | `0.1.17 β 1.0.0` | `npm run version:major` |
|
|
23
|
+
|
|
24
|
+
> Enquanto em `0.x`, **o minor Γ© o breaking change**. Mudar a estrutura das regras ou
|
|
25
|
+
> do template sobe o minor, nΓ£o o patch. O `major` fica reservado para quando a interface
|
|
26
|
+
> estabilizar e a gente for para `1.0.0`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. AutenticaΓ§Γ£o β leia isto antes de criar qualquer token
|
|
31
|
+
|
|
32
|
+
> π¨ **Com 2FA ligado na conta npm, um token comum NΓO publica.**
|
|
33
|
+
> O CI nΓ£o tem como digitar o cΓ³digo do autenticador, entΓ£o o publish falha com:
|
|
34
|
+
>
|
|
35
|
+
> ```
|
|
36
|
+
> npm error code EOTP
|
|
37
|
+
> npm error This operation requires a one-time password from your authenticator.
|
|
38
|
+
> ```
|
|
39
|
+
>
|
|
40
|
+
> Isso **nΓ£o Γ© bug do workflow** β Γ© o npm exigindo presenΓ§a humana. Existem trΓͺs
|
|
41
|
+
> saΓdas, e sΓ³ uma Γ© boa.
|
|
42
|
+
|
|
43
|
+
| Caminho | EsforΓ§o | SituaΓ§Γ£o |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| **A. Bootstrap local + OIDC** | ~5 min, uma vez | β
**Recomendado.** Sem token para sempre |
|
|
46
|
+
| **B. Token com "Bypass 2FA"** | ~2 min | β οΈ Funciona hoje, **deprecado em jan/2027** |
|
|
47
|
+
| **C. Token stage-only** | ~10 min | π‘οΈ Mais seguro, exige aprovar cada release |
|
|
48
|
+
|
|
49
|
+
### 1.1 O `404 Not Found` na primeira publicaΓ§Γ£o Γ© enganoso
|
|
50
|
+
|
|
51
|
+
> ```
|
|
52
|
+
> npm error code E404
|
|
53
|
+
> npm error 404 Not Found - PUT https://registry.npmjs.org/create-sdd-ai-stack - Not found
|
|
54
|
+
> ```
|
|
55
|
+
|
|
56
|
+
**NΓ£o Γ© preciso "criar" o pacote antes.** O `npm publish` cria o pacote sozinho na
|
|
57
|
+
primeira publicaΓ§Γ£o. O 404 nesse PUT significa que **o registry nΓ£o reconheceu vocΓͺ
|
|
58
|
+
como autorizado** β e o npm nΓ£o quer confirmar se o nome existe para quem nΓ£o tem
|
|
59
|
+
acesso.
|
|
60
|
+
|
|
61
|
+
A causa mais comum (e a que aconteceu aqui) Γ© o **`.npmrc` do projeto** declarar um
|
|
62
|
+
`_authToken` que sobrescreve o seu:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
projeto > ~/.npmrc (usuΓ‘rio) > npm global
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Com `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` no `.npmrc` do projeto e a
|
|
69
|
+
variΓ‘vel vazia, o token efetivo fica **vazio** e o sintoma Γ© dourado:
|
|
70
|
+
|
|
71
|
+
| Comando | Com token vΓ‘lido | Com token zerado |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `npm whoami` | `seu-usuario` | `401 Unauthorized` |
|
|
74
|
+
| `npm publish` (1Βͺ vez) | cria o pacote | `404 Not Found` no PUT |
|
|
75
|
+
|
|
76
|
+
**DiagnΓ³stico em 5 segundos:**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm whoami # tem que imprimir seu usuΓ‘rio, nΓ£o 401
|
|
80
|
+
npm config get "//registry.npmjs.org/:_authToken" # tem que ter tamanho > 1
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A correΓ§Γ£o Γ© **nΓ£o declarar `_authToken` no `.npmrc` do projeto** β sΓ³ `registry=`.
|
|
84
|
+
A auth vem do `~/.npmrc` (local) ou da variΓ‘vel `NODE_AUTH_TOKEN` (CI). HΓ‘ um teste
|
|
85
|
+
que trava isso: `publish: o .npmrc do projeto NΓO pode declarar _authToken`.
|
|
86
|
+
|
|
87
|
+
### π Caminho A β bootstrap local + Trusted Publishing (OIDC)
|
|
88
|
+
|
|
89
|
+
O OIDC Γ© a soluΓ§Γ£o definitiva: credencial de vida curta, assinada pelo GitHub,
|
|
90
|
+
**sem token nenhum**. Mas tem uma limitaΓ§Γ£o: **OIDC nΓ£o publica a primeira versΓ£o** de um
|
|
91
|
+
pacote β o pacote precisa existir no npm antes de vocΓͺ configurar o publisher.
|
|
92
|
+
|
|
93
|
+
Por isso o fluxo Γ© "publica uma vez local, depois nunca mais":
|
|
94
|
+
|
|
95
|
+
**Passo 1 β publique a primeira versΓ£o da sua mΓ‘quina** (vocΓͺ tem o autenticador):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm publish --access public --provenance=false --otp=123456
|
|
99
|
+
# β cΓ³digo do seu app autenticador
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> π¨ **NΓ£o coloque `provenance` no `publishConfig` do `package.json`.**
|
|
103
|
+
> O npm lΓͺ `publishConfig` **com prioridade sobre flag de CLI e sobre variΓ‘vel de
|
|
104
|
+
> ambiente**. Com `provenance: true` lΓ‘, *qualquer* publish fora de um CI com OIDC
|
|
105
|
+
> falha com:
|
|
106
|
+
>
|
|
107
|
+
> ```
|
|
108
|
+
> npm error code EUSAGE
|
|
109
|
+
> npm error Automatic provenance generation not supported for provider: null
|
|
110
|
+
> ```
|
|
111
|
+
>
|
|
112
|
+
> NΓ£o adianta passar `--provenance=false` nem `NPM_CONFIG_PROVENANCE=false`: o
|
|
113
|
+
> `publishConfig` vence os dois. O provenance Γ© controlado **por invocaΓ§Γ£o** β o
|
|
114
|
+
> workflow de release passa `--provenance` explicitamente, o publish local nΓ£o passa.
|
|
115
|
+
>
|
|
116
|
+
> O `--provenance=false` acima Γ© por clarity e por seguranΓ§a de quem roda o comando
|
|
117
|
+
> num repo onde alguΓ©m reinseriu a flag β mas o que resolve Γ© o `package.json` estar limpo.
|
|
118
|
+
|
|
119
|
+
**Passo 2 β configure o publisher confiΓ‘vel** em
|
|
120
|
+
<https://www.npmjs.com/package/create-sdd-ai-stack/settings/trusted-publishers>:
|
|
121
|
+
|
|
122
|
+
| Campo | Valor |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| Provider | GitHub Actions |
|
|
125
|
+
| Organization or user | `marcelinosandroni` |
|
|
126
|
+
| Repository | `sdd-ai-stack` |
|
|
127
|
+
| Workflow filename | `release.yml` (sΓ³ o nome, com `.yml`) |
|
|
128
|
+
| Allowed actions | `npm publish` |
|
|
129
|
+
|
|
130
|
+
> β οΈ O npm **nΓ£o valida** essa configuraΓ§Γ£o ao salvar. Errou o nome do workflow ou do
|
|
131
|
+
> repo, o erro sΓ³ aparece na hora de publicar. Tudo Γ© **case-sensitive**.
|
|
132
|
+
|
|
133
|
+
**Passo 3 β apague o secret** (deixe o OIDC Assumir):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gh secret delete NPM_TOKEN --repo marcelinosandroni/sdd-ai-stack
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Pronto. Da prΓ³xima vez em diante:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run version:patch && git push origin main && git push origin --tags
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Publica sozinho, com provenance, **sem token nenhum**. Pode atΓ© desligar
|
|
146
|
+
"Require two-factor authentication" do pacote depois β o OIDC nΓ£o depende dele.
|
|
147
|
+
|
|
148
|
+
### β οΈ Caminho B β token com "Bypass 2FA" (temporΓ‘rio)
|
|
149
|
+
|
|
150
|
+
Se quiser o CI funcionando **hoje** sem mexer na mΓ‘quina:
|
|
151
|
+
|
|
152
|
+
Em <https://www.npmjs.com/settings/access-tokens>, crie um token granular com:
|
|
153
|
+
|
|
154
|
+
| Campo | Valor |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| Permissions | **Read and write** |
|
|
157
|
+
| Bypass 2FA | β
**marcado** |
|
|
158
|
+
| Package | `create-sdd-ai-stack` |
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
gh secret set NPM_TOKEN --repo marcelinosandroni/sdd-ai-stack
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> **SΓ³ como ponte.** O npm avisa: *"a publicaΓ§Γ£o direta com token granular serΓ‘ removida
|
|
165
|
+
> em janeiro de 2027"*. AlΓ©m disso, hΓ‘ bug aberto ([npm/cli#9268](https://github.com/npm/cli/issues/9268))
|
|
166
|
+
> onde "Bypass 2FA" Γ© ignorado pelo npm 11.x. Trate como prazo, nΓ£o como soluΓ§Γ£o.
|
|
167
|
+
|
|
168
|
+
### π‘οΈ Caminho C β stage-only (mais seguro, mais atrito)
|
|
169
|
+
|
|
170
|
+
Token **Read and write (stage only)**: o CI sobe a versΓ£o, mas ela **nΓ£o vai ao ar**.
|
|
171
|
+
Um maintainer precisa aprovar com 2FA:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm stage publish # no CI, com o token stage-only
|
|
175
|
+
npm stage list # ver o que estΓ‘ pendente
|
|
176
|
+
npm stage approve --otp=123456
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Combine com `Require two-factor authentication and disallow tokens` no
|
|
180
|
+
[package settings](https://www.npmjs.com/package/create-sdd-ai-stack/settings): token
|
|
181
|
+
vazado nΓ£o consegue publicar nada sozinho. Γ a postura mΓ‘xima de seguranΓ§a β ao preΓ§o
|
|
182
|
+
de uma aprovaΓ§Γ£o manual por release.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 2. Como o workflow decide o modo
|
|
187
|
+
|
|
188
|
+
Ele nΓ£o precisa saber: **o npm escolhe sozinho**.
|
|
189
|
+
|
|
190
|
+
| `NPM_TOKEN` no repo | O que o workflow faz | Como o npm publica |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| **definido** | escreve a linha de token no `~/.npmrc` | modo token |
|
|
193
|
+
| **ausente** | nΓ£o escreve **nada** no `.npmrc` | OIDC |
|
|
194
|
+
|
|
195
|
+
> β οΈ **O passo `Publica` nΓ£o define `NODE_AUTH_TOKEN` de propΓ³sito.** O npm sΓ³ engata o
|
|
196
|
+
> OIDC quando o auth estΓ‘ **ausente**. Se `NODE_AUTH_TOKEN` estiver no ambiente, ele
|
|
197
|
+
> ignora o OIDC e tenta token β e volta a falhar.
|
|
198
|
+
|
|
199
|
+
> β οΈ **Requisito de versΓ£o:** OIDC precisa de **npm β₯ 11.5.1** e **Node β₯ 22.14.0**.
|
|
200
|
+
> O Node 22 do runner do GitHub vem com npm 10.x, entΓ£o o workflow roda
|
|
201
|
+
> `npm install -g npm@latest` antes de publicar. Sem isso o OIDC nunca engata.
|
|
202
|
+
|
|
203
|
+
> β οΈ **Nunca** cole o token num arquivo, no `.npmrc` commitado, ou num commit.
|
|
204
|
+
> Se vazar: revogue em <https://www.npmjs.com/settings/access-tokens> imediatamente.
|
|
205
|
+
|
|
206
|
+
Verificar que estΓ‘ lΓ‘:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
gh secret list --repo marcelinosandroni/sdd-ai-stack
|
|
210
|
+
# deve listar: NPM_TOKEN Updated: <data>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
No caminho A (OIDC), a lista deve estar **vazia** β e Γ© isso que faz o npm usar OIDC.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 3. Testar sem publicar (recomendado na primeira vez)
|
|
218
|
+
|
|
219
|
+
Depois que o workflow estiver na `main`, valide sem queimar versΓ£o:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
gh workflow run release.yml -f version=9.9.9 -f dry_run=true
|
|
223
|
+
gh run watch
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Isso roda testes, checa links, confere o conteΓΊdo do tarball, e faz
|
|
227
|
+
`npm publish --dry-run`. **Nada Γ© publicado.**
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 4. Publicar de verdade
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# 1. main atualizada e CI verde
|
|
235
|
+
git checkout main && git pull
|
|
236
|
+
|
|
237
|
+
# 2. bump de versΓ£o (commita o package.json e cria a tag)
|
|
238
|
+
npm run version:patch # ou version:minor / version:major
|
|
239
|
+
|
|
240
|
+
# 3. manda cΓ³digo e tag
|
|
241
|
+
git push origin main
|
|
242
|
+
git push origin --tags
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
O workflow dispara com o push da tag. Acompanhe:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
gh run list
|
|
249
|
+
gh run watch
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Se tudo der certo: <https://www.npmjs.com/package/create-sdd-ai-stack>
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 5. O que o workflow checa antes de publicar
|
|
257
|
+
|
|
258
|
+
| Guarda | Motivo |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| `npm test` (22 testes) | nunca publica com teste vermelho |
|
|
261
|
+
| `node SKILLS/check-docs/check-docs.mjs` | nenhuma regra apontando pra arquivo morto |
|
|
262
|
+
| tag `vX.Y.Z` == `version` do `package.json` | evita publicar 0.1.18 quando a tag Γ© 0.1.17 |
|
|
263
|
+
| 10 arquivos essenciais presentes no tarball | pega `files` mal configurado no `package.json` |
|
|
264
|
+
| `concurrency: release-npm` | dois publishes simultΓ’neos nΓ£o correm em paralelo |
|
|
265
|
+
| `npm >= 11.5.1` no job de publish | sem isso o OIDC nΓ£o engata (Node 22 do runner traz npm 10.x) |
|
|
266
|
+
| `publishConfig.provenance` | o pacote Γ© assinado pelo GitHub β prova de que saiu deste repo |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 6. Problemas comuns
|
|
271
|
+
|
|
272
|
+
| Sintoma | Causa | SoluΓ§Γ£o |
|
|
273
|
+
| --- | --- | --- |
|
|
274
|
+
| `E404` / "PUT ... Not Found" **na primeira publicaΓ§Γ£o** | **nΓ£o Γ© o pacote ausente** β Γ© o `.npmrc` do projeto sombreando o seu token, deixando a auth vazia | veja Β§1.1 |
|
|
275
|
+
| `EUSAGE` / "Automatic provenance generation not supported for provider: null" | `publishConfig.provenance: true` e o publish nΓ£o saiu de um CI com OIDC | tire `provenance` do `publishConfig`; no local use `--provenance=false`, no CI `--provenance` |
|
|
276
|
+
| `EOTP` / "requires a one-time password" | **2FA ligado** e o token nΓ£o tem "Bypass 2FA" | seΓ§Γ£o 1 β caminho A (OIDC) ou B (bypass) |
|
|
277
|
+
| `ENEEDAUTH` / "need auth" com OIDC configurado | `NODE_AUTH_TOKEN` presente no ambiente derruba o OIDC, **ou** o workflow nΓ£o Γ© o `release.yml`, **ou** o repo/owner estΓ‘ errado | confira os 3 campos no npmjs.com; eles sΓ£o case-sensitive |
|
|
278
|
+
| `ENOENT` / OIDC nΓ£o engata | npm < 11.5.1 ou Node < 22.14.0 | o workflow jΓ‘ sobe o npm; se persistir, atualize o `node-version` |
|
|
279
|
+
| `E403 Forbidden` | token sem permissΓ£o de escrita no pacote | regere com Read and write em `create-sdd-ai-stack` |
|
|
280
|
+
| `E_STAGE_REQUIRED` | token Γ© stage-only e vocΓͺ chamou `npm publish` | use `npm stage publish` e aprove com `npm stage approve --otp` |
|
|
281
|
+
| `401 Unauthorized` | token expirado ou revogado | regere no npm e grave de novo |
|
|
282
|
+
| `cannot publish over previously published version` | jΓ‘ existe essa versΓ£o | bump a versΓ£o (`npm run version:patch`) |
|
|
283
|
+
| `tag 'v0.1.17' nΓ£o bate com package.json '0.1.18'` | esqueceu de commitar o bump | `git add package.json && git commit -m "chore: v0.1.18"` |
|
|
284
|
+
| `faltando no pacote: X` | `files` do `package.json` incompleto | adicione o caminho em `files` |
|
|
285
|
+
|
|
286
|
+
> **Bug conhecido do npm:** token granular com "Bypass 2FA" sendo ignorado pelo npm 11.x
|
|
287
|
+
> ([npm/cli#9268](https://github.com/npm/cli/issues/9268)). Se o bypass "nΓ£o funcionar",
|
|
288
|
+
> o caminho A (OIDC) Γ© a saΓda β ele nΓ£o depende de token nenhum.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 7. Publicar localmente (escape, nΓ£o Γ© o caminho padrΓ£o)
|
|
293
|
+
|
|
294
|
+
Se precisar publicar da sua mΓ‘quina:
|
|
295
|
+
|
|
296
|
+
```powershell
|
|
297
|
+
# PowerShell β o caminho de bootstrap (com 2FA ligado)
|
|
298
|
+
npm publish --access public --provenance=false --otp=123456
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Sem 2FA, ou com token de bypass:
|
|
302
|
+
|
|
303
|
+
```powershell
|
|
304
|
+
$env:NODE_AUTH_TOKEN = "<seu token>"
|
|
305
|
+
npm publish --access public --provenance=false
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
> `--provenance=false` Γ© obrigatΓ³rio em publish local. O provenance sΓ³ pode ser gerado
|
|
309
|
+
> dentro de um CI com OIDC (GitHub Actions, GitLab CI, CircleCI).
|
|
310
|
+
|
|
311
|
+
> O `.npmrc` da raiz usa `${NODE_AUTH_TOKEN}` justamente para que o token
|
|
312
|
+
> **nunca** fique gravado em arquivo.
|
|
313
|
+
|
|
314
|
+
E confira antes:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npm run check:pack # mostra exatamente o que vai subir
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 8. Checklist antes de apertar o botΓ£o
|
|
323
|
+
|
|
324
|
+
- [ ] `npm test` verde
|
|
325
|
+
- [ ] `main` sincronizada com `origin`
|
|
326
|
+
- [ ] `NPM_TOKEN` existe e nΓ£o expirou
|
|
327
|
+
- [ ] `npm run check:pack` mostra os arquivos esperados
|
|
328
|
+
- [ ] `version:patch|minor|major` escolhido conscientemente
|
|
329
|
+
- [ ] `git push origin main` e `git push origin --tags` feitos
|
|
330
|
+
|
|
331
|
+
> **Publicar Γ© irreversΓvel.** VersΓ£o publicada nΓ£o pode ser removida (sΓ³ despublicada,
|
|
332
|
+
> e o npm nunca reutiliza o nΓΊmero). A tag tambΓ©m nΓ£o deve ser deletada.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Remove blocos de cΓ³digo cercados por ``` para nΓ£o checar links ilustrativos. */
|
|
5
|
+
function stripCodeFences(md) {
|
|
6
|
+
return md.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Coleta todos os .md a partir das raΓzes informadas (caminhos em formato posix). */
|
|
10
|
+
export function collectMarkdown(roots) {
|
|
11
|
+
const files = [];
|
|
12
|
+
const walk = (p) => {
|
|
13
|
+
if (!fs.existsSync(p)) return;
|
|
14
|
+
const stat = fs.statSync(p);
|
|
15
|
+
if (stat.isDirectory()) {
|
|
16
|
+
if (/node_modules|\.next|test-results|playwright-report/.test(p)) return;
|
|
17
|
+
for (const entry of fs.readdirSync(p)) walk(path.join(p, entry));
|
|
18
|
+
} else if (/\.md$/.test(p)) {
|
|
19
|
+
files.push(p.split(path.sep).join("/"));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
roots.forEach(walk);
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Valida todos os links relativos entre documentos markdown.
|
|
28
|
+
* @returns {Array<{file, link}>} lista de links quebrados
|
|
29
|
+
*/
|
|
30
|
+
export function checkLinks(roots, { cwd = process.cwd() } = {}) {
|
|
31
|
+
const broken = [];
|
|
32
|
+
const re = /\]\((\.{0,2}\/[^)#\s]+)(?:#[^)]*)?\)/g;
|
|
33
|
+
|
|
34
|
+
for (const file of collectMarkdown(roots)) {
|
|
35
|
+
const abs = path.resolve(cwd, file);
|
|
36
|
+
const txt = stripCodeFences(fs.readFileSync(abs, "utf8"));
|
|
37
|
+
for (const m of txt.matchAll(re)) {
|
|
38
|
+
const target = path.resolve(path.dirname(abs), m[1]);
|
|
39
|
+
if (!fs.existsSync(target)) broken.push({ file, link: m[1] });
|
|
40
|
+
}
|
|
41
|
+
} return broken;
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
5
|
+
|
|
6
|
+
/** Documentos de regra que vΓ£o para <projeto>/SDD/ */
|
|
7
|
+
export const RULE_FILES = [
|
|
8
|
+
"AGENTS.md",
|
|
9
|
+
"APP.md",
|
|
10
|
+
"APP-STACK.md",
|
|
11
|
+
"ARCHITECTURE.md",
|
|
12
|
+
"DESIGN.md",
|
|
13
|
+
"NEXT.md",
|
|
14
|
+
"NODE.md",
|
|
15
|
+
"REACT.md",
|
|
16
|
+
"README.md",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Pastas de regras que vΓ£o para <projeto>/SDD/ */
|
|
20
|
+
export const RULE_DIRS = ["stacks", "specs", "docs", "SKILLS"];
|
|
21
|
+
|
|
22
|
+
/** Nome da pasta onde as regras sΓ£o instaladas dentro do projeto consumidor. */
|
|
23
|
+
export const SDD_DIR = "SDD";
|
|
24
|
+
|
|
25
|
+
/** Templates de projeto disponΓveis. */
|
|
26
|
+
export const TEMPLATES = ["next"];
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_TEMPLATE = "next";
|
|
29
|
+
|
|
30
|
+
/** Atalhos na raiz do projeto que apontam para ./SDD/AGENTS.md */
|
|
31
|
+
export const SHORTCUTS = [
|
|
32
|
+
"AGENTS.md",
|
|
33
|
+
"CLAUDE.md",
|
|
34
|
+
"GEMINI.md",
|
|
35
|
+
".cursorrules",
|
|
36
|
+
".windsurfrules",
|
|
37
|
+
".github/copilot-instructions.md",
|
|
38
|
+
".clinerules",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** RepositΓ³rio usado quando o usuΓ‘rio pede para instalar como submodule. */
|
|
42
|
+
export const DEFAULT_SUBMODULE_URL = "https://github.com/marcelinosandroni/sdd-ai-stack.git";
|
|
43
|
+
|
|
44
|
+
/** Ignorado ao copiar regras (evita embutir a prΓ³pria lib no SDD/). */
|
|
45
|
+
export const COPY_IGNORE = new Set([
|
|
46
|
+
".git",
|
|
47
|
+
".github",
|
|
48
|
+
"node_modules",
|
|
49
|
+
".next",
|
|
50
|
+
"bin",
|
|
51
|
+
"src",
|
|
52
|
+
"lib",
|
|
53
|
+
"tests",
|
|
54
|
+
"template",
|
|
55
|
+
"package.json",
|
|
56
|
+
"package-lock.json",
|
|
57
|
+
"coverage",
|
|
58
|
+
"test-results",
|
|
59
|
+
"playwright-report",
|
|
60
|
+
]);
|
package/lib/scaffold.mjs
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
COPY_IGNORE,
|
|
6
|
+
PKG_ROOT,
|
|
7
|
+
RULE_DIRS,
|
|
8
|
+
RULE_FILES,
|
|
9
|
+
SDD_DIR,
|
|
10
|
+
SHORTCUTS,
|
|
11
|
+
} from "./constants.mjs";
|
|
12
|
+
|
|
13
|
+
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
14
|
+
Helpers
|
|
15
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
16
|
+
|
|
17
|
+
export function ensureDir(dir) {
|
|
18
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function copyDir(from, to, { ignore = new Set() } = {}) {
|
|
22
|
+
ensureDir(to);
|
|
23
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
24
|
+
if (ignore.has(entry.name)) continue;
|
|
25
|
+
const src = path.join(from, entry.name);
|
|
26
|
+
const dest = path.join(to, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
copyDir(src, dest, { ignore });
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
ensureDir(path.dirname(dest));
|
|
31
|
+
fs.copyFileSync(src, dest);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Cria symlink relativo. Retorna "symlink" ou null se o SO nΓ£o permitir. */
|
|
37
|
+
function trySymlink(targetAbs, linkPath) {
|
|
38
|
+
const rel = path.relative(path.dirname(linkPath), targetAbs).split(path.sep).join("/");
|
|
39
|
+
try {
|
|
40
|
+
fs.symlinkSync(rel, linkPath, "file");
|
|
41
|
+
return "symlink";
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const STUB_BODY = `# π€ AGENTS.md (ponteiro)
|
|
48
|
+
|
|
49
|
+
> Este arquivo Γ© um **atalho** para as regras do projeto.
|
|
50
|
+
> A fonte da verdade vive em **[SDD/AGENTS.md](./SDD/AGENTS.md)**.
|
|
51
|
+
|
|
52
|
+
## π¨ LEIA AGORA
|
|
53
|
+
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
cat SDD/AGENTS.md
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
**Leitura mΓnima obrigatΓ³ria para comeΓ§ar qualquer tarefa:**
|
|
59
|
+
|
|
60
|
+
1. \`SDD/AGENTS.md\` β leis do agente e fluxo de entrega
|
|
61
|
+
2. \`SDD/specs/PLAN.md\` β a task que estΓ‘ sendo feita AGORA
|
|
62
|
+
3. \`SDD/APP.md\` + \`SDD/APP-STACK.md\` β o que Γ© este app e qual a stack
|
|
63
|
+
4. O doc de regra da stack: \`SDD/NEXT.md\` (padrΓ£o), \`SDD/NODE.md\`, \`SDD/REACT.md\`
|
|
64
|
+
5. \`SDD/DESIGN.md\` β sΓ³ se for mexer em UI
|
|
65
|
+
6. \`SDD/stacks/README.md\` β Γndice das regras por ferramenta
|
|
66
|
+
|
|
67
|
+
> **NΓ£o edite os arquivos em \`SDD/\`** sem pedir ao humano. Eles sΓ£o a lei do template.
|
|
68
|
+
> Para atualizar: \`git submodule update --remote --merge SDD\` (se submodule) ou \`npm run sdd:sync\`.
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
function writeShortcut(root, relPath, mode) {
|
|
72
|
+
const linkPath = path.join(root, relPath);
|
|
73
|
+
// caminho ABSOLUTO do destino β path.relative() exige isso,
|
|
74
|
+
// senΓ£o o segundo argumento Γ© resolvido contra process.cwd() e o link nasce quebrado
|
|
75
|
+
const targetAbs = path.join(root, SDD_DIR, "AGENTS.md");
|
|
76
|
+
ensureDir(path.dirname(linkPath));
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(linkPath)) return "skipped";
|
|
79
|
+
if (mode === "stub") return writeStub(linkPath);
|
|
80
|
+
|
|
81
|
+
return trySymlink(targetAbs, linkPath) ?? writeStub(linkPath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeStub(linkPath) {
|
|
85
|
+
fs.writeFileSync(linkPath, STUB_BODY, "utf8");
|
|
86
|
+
return "stub";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function run(cmd, args, cwd) {
|
|
90
|
+
execFileSync(cmd, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function has(cmd) {
|
|
94
|
+
try {
|
|
95
|
+
execFileSync(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
103
|
+
Installers
|
|
104
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
105
|
+
|
|
106
|
+
/** Copia as regras do template para <target>/SDD/ */
|
|
107
|
+
export function installRules(target, { log = () => {} } = {}) {
|
|
108
|
+
const dest = path.join(target, SDD_DIR);
|
|
109
|
+
ensureDir(dest);
|
|
110
|
+
|
|
111
|
+
for (const file of RULE_FILES) {
|
|
112
|
+
const src = path.join(PKG_ROOT, file);
|
|
113
|
+
if (fs.existsSync(src)) {
|
|
114
|
+
fs.copyFileSync(src, path.join(dest, file));
|
|
115
|
+
log(` β SDD/${file}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const dir of RULE_DIRS) {
|
|
120
|
+
const src = path.join(PKG_ROOT, dir);
|
|
121
|
+
if (!fs.existsSync(src)) continue;
|
|
122
|
+
copyDir(src, path.join(dest, dir), { ignore: COPY_IGNORE });
|
|
123
|
+
log(` β SDD/${dir}/`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return dest;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Instala a pasta SDD/ como git submodule apontando para o repo remoto. */
|
|
130
|
+
export function installSubmodule(target, url, { log = () => {} } = {}) {
|
|
131
|
+
const dest = path.join(target, SDD_DIR);
|
|
132
|
+
if (!has("git")) {
|
|
133
|
+
log(" ! git nΓ£o encontrado. Copiando as regras localmente.");
|
|
134
|
+
return installRules(target, { log });
|
|
135
|
+
}
|
|
136
|
+
ensureDir(target);
|
|
137
|
+
run("git", ["submodule", "add", url, SDD_DIR], target);
|
|
138
|
+
installShortcuts(target, { log });
|
|
139
|
+
return dest;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Cria os atalhos na raiz do projeto apontando para ./SDD/AGENTS.md */
|
|
143
|
+
export function installShortcuts(target, { log = () => {}, mode = "auto" } = {}) {
|
|
144
|
+
const results = SHORTCUTS.map((rel) => ({ rel, kind: writeShortcut(target, rel, mode) }));
|
|
145
|
+
for (const { rel, kind } of results) {
|
|
146
|
+
log(` β ${rel} (${kind})`);
|
|
147
|
+
}
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Copia o template de projeto (Next.js) para o alvo. */
|
|
152
|
+
export function installTemplate(target, template, { log = () => {} } = {}) {
|
|
153
|
+
const src = path.join(PKG_ROOT, "template", template);
|
|
154
|
+
if (!fs.existsSync(src)) {
|
|
155
|
+
throw new Error(`Template "${template}" nΓ£o encontrado em ${src}`);
|
|
156
|
+
}
|
|
157
|
+
copyDir(src, target, { ignore: new Set([".git"]) });
|
|
158
|
+
restoreGitignore(target);
|
|
159
|
+
log(` β template/${template} β ${target}`);
|
|
160
|
+
return target;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* O npm NUNCA empacota arquivo chamado `.gitignore` (Γ© um default-ignore dele).
|
|
165
|
+
* Guardar como `gitignore` e renomear aqui garante que o app gerado SEMPRE
|
|
166
|
+
* tenha .gitignore β sem ele, `.env.local` e `.next` iam pro git.
|
|
167
|
+
*/
|
|
168
|
+
function restoreGitignore(target) {
|
|
169
|
+
const from = path.join(target, "gitignore");
|
|
170
|
+
const to = path.join(target, ".gitignore");
|
|
171
|
+
if (fs.existsSync(from)) {
|
|
172
|
+
fs.renameSync(from, to);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (fs.existsSync(to)) return;
|
|
176
|
+
throw new Error("Template sem gitignore β o app gerado ficaria sem .gitignore.");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
180
|
+
OrquestraΓ§Γ£o
|
|
181
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Cria um projeto completo: template + regras + atalhos.
|
|
185
|
+
* @returns {object} resumo do que foi feito
|
|
186
|
+
*/
|
|
187
|
+
export function scaffold(options) {
|
|
188
|
+
const {
|
|
189
|
+
target,
|
|
190
|
+
template = "next",
|
|
191
|
+
install = false,
|
|
192
|
+
git = false,
|
|
193
|
+
submodule = null,
|
|
194
|
+
shortcutMode = "auto",
|
|
195
|
+
log = console.log,
|
|
196
|
+
} = options;
|
|
197
|
+
|
|
198
|
+
const abs = path.resolve(target);
|
|
199
|
+
const summary = { target: abs, template, rules: false, shortcuts: [], git: false, install: false };
|
|
200
|
+
|
|
201
|
+
if (fs.existsSync(abs) && fs.readdirSync(abs).length > 0) {
|
|
202
|
+
throw new Error(`Pasta "${abs}" jΓ‘ existe e nΓ£o estΓ‘ vazia. Escolha outro nome.`);
|
|
203
|
+
}
|
|
204
|
+
ensureDir(abs);
|
|
205
|
+
|
|
206
|
+
// 1. Template do app (primeiro, para nΓ£o colidir com SDD/)
|
|
207
|
+
if (template && template !== "none") {
|
|
208
|
+
log(`\nπ¦ Template: ${template}`);
|
|
209
|
+
installTemplate(abs, template, { log });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 2. Regras
|
|
213
|
+
log("\nπ§ Regras SDD:");
|
|
214
|
+
if (submodule) {
|
|
215
|
+
log(` β instalando como submodule: ${submodule}`);
|
|
216
|
+
installSubmodule(abs, submodule, { log });
|
|
217
|
+
} else {
|
|
218
|
+
installRules(abs, { log });
|
|
219
|
+
installShortcuts(abs, { log, mode: shortcutMode });
|
|
220
|
+
}
|
|
221
|
+
summary.rules = true;
|
|
222
|
+
|
|
223
|
+
// 3. Git
|
|
224
|
+
if (git) {
|
|
225
|
+
if (has("git")) {
|
|
226
|
+
log("\nπΏ Git:");
|
|
227
|
+
run("git", ["init"], abs);
|
|
228
|
+
run("git", ["add", "-A"], abs);
|
|
229
|
+
run(
|
|
230
|
+
"git",
|
|
231
|
+
["-c", "user.name=Marcelino Sandroni", "-c", "user.email=marcelino.sandroni@gmail.com",
|
|
232
|
+
"commit", "-m", `chore: scaffold inicial com SDD AI Stack. (Agent: create-sdd-ai-stack)`],
|
|
233
|
+
abs,
|
|
234
|
+
);
|
|
235
|
+
summary.git = true;
|
|
236
|
+
} else {
|
|
237
|
+
log("\n! git nΓ£o encontrado. Pulando init.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 4. DependΓͺncias
|
|
242
|
+
if (install) {
|
|
243
|
+
if (fs.existsSync(path.join(abs, "package.json"))) {
|
|
244
|
+
log("\nπ₯ Instalando dependΓͺnciasβ¦");
|
|
245
|
+
run("npm", ["install"], abs);
|
|
246
|
+
summary.install = true;
|
|
247
|
+
} else {
|
|
248
|
+
log("\n! Sem package.json β pulando instalaΓ§Γ£o.");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return summary;
|
|
253
|
+
}
|