@carlos-iso/versioner 0.0.2
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/LICENSE +19 -0
- package/README.md +290 -0
- package/bin/versioner.js +3 -0
- package/package.json +50 -0
- package/src/cli.js +21 -0
- package/src/commands/BaseCommand.js +33 -0
- package/src/commands/BuildCommand.js +9 -0
- package/src/commands/HelpCommand.js +77 -0
- package/src/commands/InitCommand.js +197 -0
- package/src/commands/MajorCommand.js +9 -0
- package/src/commands/MinorCommand.js +9 -0
- package/src/commands/StatusCommand.js +118 -0
- package/src/commands/VersionCommand.js +63 -0
- package/src/constants/index.js +150 -0
- package/src/core/Context.js +65 -0
- package/src/index.js +28 -0
- package/src/managers/ConfigManager.js +70 -0
- package/src/managers/FileManager.js +75 -0
- package/src/managers/GitManager.js +135 -0
- package/src/managers/ReleaseManager.js +301 -0
- package/src/managers/VersionManager.js +126 -0
- package/src/services/CommandRouter.js +107 -0
- package/src/test/smoke.js +156 -0
- package/src/utils/args.js +60 -0
- package/src/utils/file.js +80 -0
- package/src/utils/logger.js +103 -0
- package/src/utils/object.js +79 -0
- package/src/utils/time.js +36 -0
- package/src/utils/utils.js +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 <copyright holders>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Versioner
|
|
2
|
+
|
|
3
|
+
CLI que substitui o fluxo manual de release por um único comando.
|
|
4
|
+
|
|
5
|
+
Antes:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# edita package.json
|
|
9
|
+
# edita app.json
|
|
10
|
+
git add .
|
|
11
|
+
git commit -m "v1.4.273 - Corrige login"
|
|
12
|
+
git push
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Depois:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
versioner build "Corrige login"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Sem dependências externas. Só Node.js e Git.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Instalação
|
|
26
|
+
|
|
27
|
+
No projeto:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -D @carloscoding/versioner
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Sem instalar:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx @carloscoding/versioner init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Global:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g @carloscoding/versioner
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requer Node.js 16 ou superior.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Início rápido
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd meu-projeto
|
|
53
|
+
npx @carloscoding/versioner init
|
|
54
|
+
npx versioner build "primeira release"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
O `init` cria dois arquivos, detecta o `package.json` e o `app.json`, e pergunta a versão inicial.
|
|
58
|
+
|
|
59
|
+
Se estiver usando como dependência do projeto, adicione atalhos no `package.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "versioner build",
|
|
65
|
+
"minor": "versioner minor",
|
|
66
|
+
"major": "versioner major"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
E use:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run build -- "Corrige login"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Modelo de versionamento
|
|
80
|
+
|
|
81
|
+
Formato:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
major.minor.build
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Exemplo: `2.14.583`
|
|
88
|
+
|
|
89
|
+
### Build
|
|
90
|
+
|
|
91
|
+
Incrementa em **toda** release e **nunca** volta para zero. Representa o total de releases já publicadas.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
2.14.583 → 2.14.584 → 2.14.585
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Minor
|
|
98
|
+
|
|
99
|
+
Pequenas evoluções dentro de uma mesma versão principal. Zera quando ocorre um Major.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
2.14.583 → 2.15.584
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Major
|
|
106
|
+
|
|
107
|
+
Grandes marcos: primeira versão pública, reescrita, nova arquitetura. Definido manualmente.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
2.14.583 → 3.0.584
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
O Build continua incrementando normalmente mesmo em um Major.
|
|
114
|
+
|
|
115
|
+
### Por que não SemVer
|
|
116
|
+
|
|
117
|
+
O SemVer descreve compatibilidade de API. Este modelo descreve o **histórico do projeto**: quantas grandes versões existiram, quantas evoluções cada uma recebeu e quantas releases foram publicadas desde o início.
|
|
118
|
+
|
|
119
|
+
O formato continua compatível com `x.y.z`, então o `package.json` permanece válido.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Comandos
|
|
124
|
+
|
|
125
|
+
| Comando | O que faz |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `versioner init` | Inicializa o Versioner no projeto |
|
|
128
|
+
| `versioner build "msg"` | Incrementa a Build e publica a release |
|
|
129
|
+
| `versioner minor "msg"` | Incrementa Minor e Build |
|
|
130
|
+
| `versioner major "msg"` | Incrementa Major, zera Minor, incrementa Build |
|
|
131
|
+
| `versioner version` | Mostra a versão atual |
|
|
132
|
+
| `versioner status` | Mostra versão, arquivos monitorados e estado do Git |
|
|
133
|
+
| `versioner help [comando]` | Ajuda |
|
|
134
|
+
|
|
135
|
+
Aliases: `b`, `m`, `M`, `i`, `v`, `s`, `h`.
|
|
136
|
+
|
|
137
|
+
### Flags de release
|
|
138
|
+
|
|
139
|
+
| Flag | Efeito |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `--no-push` | Faz commit mas não envia para o remoto |
|
|
142
|
+
| `--no-git` | Só versiona os arquivos, não toca no Git |
|
|
143
|
+
| `--tag` | Cria uma tag Git para a release |
|
|
144
|
+
| `--no-tag` | Desativa a tag mesmo se ligada na config |
|
|
145
|
+
| `--dry-run` | Simula tudo sem gravar nada |
|
|
146
|
+
|
|
147
|
+
### Flags do init
|
|
148
|
+
|
|
149
|
+
| Flag | Efeito |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `--yes`, `-y` | Usa os padrões sem perguntar |
|
|
152
|
+
| `--force`, `-f` | Sobrescreve arquivos existentes |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Arquivos gerados
|
|
157
|
+
|
|
158
|
+
### `.versioner.json`
|
|
159
|
+
|
|
160
|
+
O contador de versão do projeto.
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"major": 1,
|
|
165
|
+
"minor": 4,
|
|
166
|
+
"build": 273
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### `versioner.config.json`
|
|
171
|
+
|
|
172
|
+
Define o comportamento e quais arquivos recebem a versão.
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"versionFile": ".versioner.json",
|
|
177
|
+
"files": [
|
|
178
|
+
{
|
|
179
|
+
"path": "package.json",
|
|
180
|
+
"field": "version"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"path": "app.json",
|
|
184
|
+
"field": "expo.version"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"commit": {
|
|
188
|
+
"template": "v{version} - {message}",
|
|
189
|
+
"minLength": 3,
|
|
190
|
+
"maxLength": 100
|
|
191
|
+
},
|
|
192
|
+
"git": {
|
|
193
|
+
"enabled": true,
|
|
194
|
+
"add": true,
|
|
195
|
+
"commit": true,
|
|
196
|
+
"push": true,
|
|
197
|
+
"tag": false,
|
|
198
|
+
"tagPrefix": "v",
|
|
199
|
+
"tagMessage": "Release {version}"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**`files`** — qualquer arquivo `.json`. O campo `field` aceita caminhos aninhados:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
version
|
|
208
|
+
expo.version
|
|
209
|
+
project.meta.version
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**`commit.template`** — aceita `{version}`, `{message}` e `{type}`.
|
|
213
|
+
|
|
214
|
+
Arquivos de código (`app.config.js`, `app.config.ts`) são detectados pelo `init` mas **não** são alterados automaticamente. Nesses casos, leia a versão do JSON:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
const { version } = require("./package.json");
|
|
218
|
+
|
|
219
|
+
export default {
|
|
220
|
+
expo: { version },
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Segurança da release
|
|
227
|
+
|
|
228
|
+
Se qualquer etapa falhar no meio do caminho, o Versioner reverte o arquivo de versão e todos os arquivos alterados. Nada fica commitado pela metade.
|
|
229
|
+
|
|
230
|
+
O `push` é ignorado com um aviso quando não existe remoto configurado, em vez de derrubar a release. Se a branch ainda não tiver upstream, o Versioner usa `--set-upstream origin <branch>` automaticamente.
|
|
231
|
+
|
|
232
|
+
Mensagens de commit são passadas por `execFile` com array de argumentos, então aspas, `$` e crases não quebram nem executam nada.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Uso programático
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
const { ReleaseManager, createContext } = require("@carloscoding/versioner");
|
|
240
|
+
|
|
241
|
+
const context = createContext({ type: "build", message: "Release automática" });
|
|
242
|
+
|
|
243
|
+
new ReleaseManager().run(context);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Também são exportados `VersionManager`, `ConfigManager`, `FileManager`, `GitManager`, `CommandRouter`, `logger` e `constants`.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Arquitetura
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
bin/versioner.js Binário da CLI (shebang)
|
|
254
|
+
src/cli.js Bootstrap da CLI e tratamento de erro
|
|
255
|
+
src/index.js Entrada programática (require do pacote)
|
|
256
|
+
src/services/ CommandRouter (mapa nome → classe)
|
|
257
|
+
src/commands/ Um arquivo por comando
|
|
258
|
+
src/managers/ Release, Version, Config, File, Git
|
|
259
|
+
src/core/Context.js Objeto compartilhado da execução
|
|
260
|
+
src/utils/ file, object, time, args, logger
|
|
261
|
+
src/constants/ Valores fixos e metadados de ajuda
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Regra central: nenhum Manager conhece outro Manager. Todos leem e escrevem apenas no `Context`. O `ReleaseManager` só orquestra a sequência:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
validate → loadConfig → version → updateFiles → git → finish
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Desenvolvimento
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm test # teste de fumaça em um repositório Git temporário
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Para logs completos de erro:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
VERSIONER_DEBUG=1 versioner build "teste"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Cores são desativadas automaticamente com `NO_COLOR=1` ou fora de um TTY.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Licença
|
|
289
|
+
|
|
290
|
+
MIT.
|
package/bin/versioner.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carlos-iso/versioner",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CLI para automatizar versionamento e fluxo de Git usando um modelo de versionamento baseado em marcos do projeto (major.minor.build).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Carlos-iso",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"versioner": "bin/versioner.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"src",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node ./bin/versioner.js",
|
|
23
|
+
"dev": "node ./bin/versioner.js",
|
|
24
|
+
"help": "node ./bin/versioner.js help",
|
|
25
|
+
"test": "node ./test/smoke.js",
|
|
26
|
+
"prepublishOnly": "node ./src/test/smoke.js"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"version",
|
|
30
|
+
"versioning",
|
|
31
|
+
"release",
|
|
32
|
+
"git",
|
|
33
|
+
"automation",
|
|
34
|
+
"cli",
|
|
35
|
+
"build",
|
|
36
|
+
"semver",
|
|
37
|
+
"expo"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/Carlos-iso/versioner.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Carlos-iso/versioner",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/Carlos-iso/versioner/issues"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const CommandRouter = require("./services/CommandRouter");
|
|
2
|
+
const logger = require("./utils/logger");
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const router = new CommandRouter();
|
|
6
|
+
|
|
7
|
+
const code = await router.run(process.argv.slice(2));
|
|
8
|
+
|
|
9
|
+
process.exit(code || 0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
main().catch((error) => {
|
|
13
|
+
logger.break();
|
|
14
|
+
logger.error(error.message);
|
|
15
|
+
|
|
16
|
+
if (process.env.VERSIONER_DEBUG) {
|
|
17
|
+
console.error(error);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const ReleaseManager = require("../managers/ReleaseManager");
|
|
2
|
+
const { createContext } = require("../core/Context");
|
|
3
|
+
const { parse, toMessage } = require("../utils/args");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base dos comandos de release (build, minor, major).
|
|
7
|
+
* Monta o Context e delega tudo ao ReleaseManager.
|
|
8
|
+
*/
|
|
9
|
+
class BaseCommand {
|
|
10
|
+
/**
|
|
11
|
+
* Sobrescrito pelas subclasses: "build" | "minor" | "major".
|
|
12
|
+
*/
|
|
13
|
+
get type() {
|
|
14
|
+
throw new Error("Comando sem tipo definido.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async run(args = []) {
|
|
18
|
+
const { values, flags } = parse(args);
|
|
19
|
+
|
|
20
|
+
const context = createContext({
|
|
21
|
+
type: this.type,
|
|
22
|
+
message: toMessage(values),
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
flags,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const release = new ReleaseManager(context.cwd);
|
|
28
|
+
|
|
29
|
+
return release.run(context);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = BaseCommand;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const logger = require("../utils/logger");
|
|
2
|
+
const { parse } = require("../utils/args");
|
|
3
|
+
const { COMMANDS, ALIASES } = require("../constants");
|
|
4
|
+
|
|
5
|
+
class HelpCommand {
|
|
6
|
+
async run(args = []) {
|
|
7
|
+
const { values } = parse(args);
|
|
8
|
+
|
|
9
|
+
const target = values[0];
|
|
10
|
+
|
|
11
|
+
if (target) {
|
|
12
|
+
return this.detail(ALIASES[target] || target);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return this.overview();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
overview() {
|
|
19
|
+
logger.title("Versioner");
|
|
20
|
+
|
|
21
|
+
logger.muted(" Automatiza versionamento e fluxo de Git em um único comando.");
|
|
22
|
+
|
|
23
|
+
logger.section("Uso");
|
|
24
|
+
logger.muted(" versioner <comando> [argumentos] [flags]");
|
|
25
|
+
|
|
26
|
+
logger.section("Comandos");
|
|
27
|
+
|
|
28
|
+
const width = Math.max(...COMMANDS.map((command) => command.name.length)) + 4;
|
|
29
|
+
|
|
30
|
+
for (const command of COMMANDS) {
|
|
31
|
+
logger.write(` ${logger.bold(command.name.padEnd(width))}${command.summary}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logger.section("Exemplos");
|
|
35
|
+
logger.muted(" versioner init");
|
|
36
|
+
logger.muted(' versioner build "Corrige o login"');
|
|
37
|
+
logger.muted(' versioner minor "Adiciona filtros na busca"');
|
|
38
|
+
logger.muted(' versioner major "Nova arquitetura" --tag');
|
|
39
|
+
logger.muted(" versioner status");
|
|
40
|
+
|
|
41
|
+
logger.section("Ajuda de um comando");
|
|
42
|
+
logger.muted(" versioner help build");
|
|
43
|
+
logger.break();
|
|
44
|
+
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
detail(name) {
|
|
49
|
+
const command = COMMANDS.find((item) => item.name === name);
|
|
50
|
+
|
|
51
|
+
if (!command) {
|
|
52
|
+
logger.error(`Comando desconhecido: ${name}`);
|
|
53
|
+
logger.muted(' Use "versioner help" para ver a lista de comandos.');
|
|
54
|
+
logger.break();
|
|
55
|
+
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger.title(`Versioner · ${command.name}`);
|
|
60
|
+
|
|
61
|
+
logger.muted(` ${command.summary}`);
|
|
62
|
+
|
|
63
|
+
logger.section("Uso");
|
|
64
|
+
logger.muted(` ${command.usage}`);
|
|
65
|
+
|
|
66
|
+
if (command.details.length) {
|
|
67
|
+
logger.section("Detalhes");
|
|
68
|
+
command.details.forEach((line) => logger.muted(` ${line}`));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.break();
|
|
72
|
+
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = HelpCommand;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
const readline = require("readline");
|
|
2
|
+
|
|
3
|
+
const ConfigManager = require("../managers/ConfigManager");
|
|
4
|
+
const VersionManager = require("../managers/VersionManager");
|
|
5
|
+
const GitManager = require("../managers/GitManager");
|
|
6
|
+
|
|
7
|
+
const logger = require("../utils/logger");
|
|
8
|
+
const { resolve, exists, readJSON } = require("../utils/file");
|
|
9
|
+
const { parse, hasFlag } = require("../utils/args");
|
|
10
|
+
const {
|
|
11
|
+
CONFIG_FILE,
|
|
12
|
+
VERSION_FILE,
|
|
13
|
+
DEFAULT_CONFIG,
|
|
14
|
+
DEFAULT_VERSION,
|
|
15
|
+
DETECTABLE_FILES,
|
|
16
|
+
} = require("../constants");
|
|
17
|
+
|
|
18
|
+
class InitCommand {
|
|
19
|
+
async run(args = []) {
|
|
20
|
+
const { flags } = parse(args);
|
|
21
|
+
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
|
|
24
|
+
const auto = hasFlag(flags, "yes");
|
|
25
|
+
const force = hasFlag(flags, "force");
|
|
26
|
+
|
|
27
|
+
const configManager = new ConfigManager(cwd);
|
|
28
|
+
const versionManager = new VersionManager(cwd, VERSION_FILE);
|
|
29
|
+
const gitManager = new GitManager(cwd);
|
|
30
|
+
|
|
31
|
+
logger.title("Versioner · init");
|
|
32
|
+
|
|
33
|
+
// 1. Repositório Git
|
|
34
|
+
if (!gitManager.isInstalled()) {
|
|
35
|
+
logger.error("Git não encontrado no sistema.");
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!gitManager.isRepository()) {
|
|
40
|
+
logger.warn('Nenhum repositório Git encontrado. Rode "git init" antes de publicar.');
|
|
41
|
+
} else {
|
|
42
|
+
logger.success(`Repositório Git detectado (branch: ${gitManager.branch()}).`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Arquivos já existentes
|
|
46
|
+
if ((configManager.exists() || versionManager.exists()) && !force) {
|
|
47
|
+
logger.error(
|
|
48
|
+
`O Versioner já está inicializado neste projeto. Use --force para sobrescrever.`,
|
|
49
|
+
);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Detecção de arquivos
|
|
54
|
+
const detected = this.detect(cwd);
|
|
55
|
+
|
|
56
|
+
if (detected.supported.length) {
|
|
57
|
+
logger.success("Arquivos detectados:");
|
|
58
|
+
detected.supported.forEach((file) => logger.item(`${file.path} (${file.field})`));
|
|
59
|
+
} else {
|
|
60
|
+
logger.warn("Nenhum arquivo JSON de versão foi detectado.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
detected.unsupported.forEach((file) => {
|
|
64
|
+
logger.warn(
|
|
65
|
+
`${file.path} detectado, mas arquivos de código não são versionados automaticamente.`,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 4. Versão inicial
|
|
70
|
+
const initial = auto
|
|
71
|
+
? this.fromPackage(cwd) || { ...DEFAULT_VERSION }
|
|
72
|
+
: await this.ask(cwd);
|
|
73
|
+
|
|
74
|
+
// 5. Gravação
|
|
75
|
+
const config = {
|
|
76
|
+
...DEFAULT_CONFIG,
|
|
77
|
+
versionFile: VERSION_FILE,
|
|
78
|
+
files: detected.supported.map((file) => ({
|
|
79
|
+
path: file.path,
|
|
80
|
+
field: file.field,
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
configManager.save(config);
|
|
85
|
+
versionManager.save(initial);
|
|
86
|
+
|
|
87
|
+
logger.break();
|
|
88
|
+
logger.success(`${CONFIG_FILE} criado.`);
|
|
89
|
+
logger.success(`${VERSION_FILE} criado.`);
|
|
90
|
+
|
|
91
|
+
logger.section("Versão inicial");
|
|
92
|
+
logger.field("Versão", versionManager.toString(initial));
|
|
93
|
+
|
|
94
|
+
logger.section("Próximo passo");
|
|
95
|
+
logger.muted(' versioner build "primeira release"');
|
|
96
|
+
logger.break();
|
|
97
|
+
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Procura arquivos conhecidos no diretório atual.
|
|
103
|
+
*/
|
|
104
|
+
detect(cwd) {
|
|
105
|
+
const supported = [];
|
|
106
|
+
const unsupported = [];
|
|
107
|
+
|
|
108
|
+
for (const file of DETECTABLE_FILES) {
|
|
109
|
+
if (!exists(resolve(cwd, file.path))) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (file.json) {
|
|
114
|
+
supported.push(file);
|
|
115
|
+
} else {
|
|
116
|
+
unsupported.push(file);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { supported, unsupported };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Tenta reaproveitar a versão já presente no package.json.
|
|
125
|
+
*/
|
|
126
|
+
fromPackage(cwd) {
|
|
127
|
+
const filepath = resolve(cwd, "package.json");
|
|
128
|
+
|
|
129
|
+
if (!exists(filepath)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const version = readJSON(filepath).version;
|
|
135
|
+
|
|
136
|
+
return this.parseVersion(version);
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
parseVersion(value) {
|
|
143
|
+
const match = String(value || "").match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
144
|
+
|
|
145
|
+
if (!match) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
major: Number(match[1]),
|
|
151
|
+
minor: Number(match[2]),
|
|
152
|
+
build: Number(match[3]),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Pergunta interativa da versão inicial.
|
|
158
|
+
*/
|
|
159
|
+
async ask(cwd) {
|
|
160
|
+
const suggestion = this.fromPackage(cwd) || { ...DEFAULT_VERSION };
|
|
161
|
+
|
|
162
|
+
const suggested = `${suggestion.major}.${suggestion.minor}.${suggestion.build}`;
|
|
163
|
+
|
|
164
|
+
logger.break();
|
|
165
|
+
|
|
166
|
+
const answer = await this.prompt(`Versão inicial [${suggested}]: `);
|
|
167
|
+
|
|
168
|
+
if (!answer.trim()) {
|
|
169
|
+
return suggestion;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parsed = this.parseVersion(answer.trim().replace(/^v/i, ""));
|
|
173
|
+
|
|
174
|
+
if (!parsed) {
|
|
175
|
+
logger.warn(`Formato inválido. Usando ${suggested}.`);
|
|
176
|
+
return suggestion;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return parsed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
prompt(question) {
|
|
183
|
+
const rl = readline.createInterface({
|
|
184
|
+
input: process.stdin,
|
|
185
|
+
output: process.stdout,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return new Promise((resolveAnswer) => {
|
|
189
|
+
rl.question(question, (answer) => {
|
|
190
|
+
rl.close();
|
|
191
|
+
resolveAnswer(answer);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = InitCommand;
|