@kaikybrofc/omnizap-system 2.1.9 → 2.2.0

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/.env.example CHANGED
@@ -521,6 +521,13 @@ DEPLOY_PACKAGE_SECONDARY_TOKEN_KEYS=
521
521
  # ==============================
522
522
  # Release (auto commit/push)
523
523
  # ==============================
524
+ # Tipo de bump padrão no npm run release (patch/minor/major...)
525
+ RELEASE_TYPE=patch
526
+ # Opcional: força versão exata (ex: 2.2.0), ignorando RELEASE_TYPE
527
+ RELEASE_FORCE_VERSION=
528
+ # Rollover automático de patch: quando patch >= RELEASE_PATCH_ROLLOVER_AT, vira (major).(minor+1).0
529
+ RELEASE_PATCH_ROLLOVER_ENABLED=1
530
+ RELEASE_PATCH_ROLLOVER_AT=10
524
531
  # Faz commit automatico de alteracoes pendentes antes do release
525
532
  RELEASE_GIT_AUTO_COMMIT=1
526
533
  # Faz push automatico dos commits gerados no release
@@ -532,3 +539,27 @@ RELEASE_GIT_BRANCH=
532
539
  RELEASE_GIT_PRE_COMMIT_MESSAGE=chore(release): auto-commit before release
533
540
  RELEASE_GIT_COMMIT_VERSION=1
534
541
  RELEASE_GIT_VERSION_COMMIT_PREFIX=chore(release): v
542
+
543
+ # Cria/atualiza GitHub Release (aba Releases) automaticamente no npm run release
544
+ RELEASE_GITHUB_RELEASE=1
545
+ # Se 1, falha o release caso GitHub Release esteja desativado
546
+ RELEASE_REQUIRE_GITHUB_RELEASE=1
547
+ RELEASE_GITHUB_REPO=kaikybrofc/omnizap-system
548
+ RELEASE_GITHUB_TOKEN=
549
+ RELEASE_GITHUB_TAG_PREFIX=v
550
+ RELEASE_GITHUB_NAME_PREFIX=v
551
+ RELEASE_GITHUB_GENERATE_NOTES=1
552
+ # Opcional: 1 para prerelease, 0 para release normal, vazio = auto (detecta '-' na versao)
553
+ RELEASE_GITHUB_PRERELEASE=
554
+ RELEASE_GITHUB_DRAFT=0
555
+ # Opcional: commit/tag alvo (vazio usa HEAD atual)
556
+ RELEASE_GITHUB_TARGET=
557
+
558
+ # Se 1, exige publish em ambos os registries no release (GitHub Packages + npmjs)
559
+ RELEASE_REQUIRE_DUAL_PUBLISH=1
560
+ # Verificação final: local + registries + GitHub Release na mesma versão
561
+ RELEASE_VERIFY_UNIFIED_VERSION=1
562
+ RELEASE_VERIFY_PRIMARY_REGISTRY=https://npm.pkg.github.com
563
+ RELEASE_VERIFY_SECONDARY_REGISTRY=https://registry.npmjs.org
564
+ RELEASE_VERIFY_PRIMARY_TOKEN_KEYS=DEPLOY_PACKAGE_TOKEN,DEPLOY_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN
565
+ RELEASE_VERIFY_SECONDARY_TOKEN_KEYS=DEPLOY_PACKAGE_SECONDARY_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN
package/README.md CHANGED
@@ -132,7 +132,7 @@ Variáveis legadas foram mantidas por compatibilidade (`QUOTE_API_URL`, `WAIFU_A
132
132
  - `npm run pm2:prod`: sobe com PM2 usando `ecosystem.prod.config.cjs`.
133
133
  - `npm run deploy`: deploy automático de `public/` com cache-bust, backup, validação e reload do Nginx.
134
134
  - `npm run deploy:dry-run`: simula o deploy sem publicar/recarregar serviços.
135
- - `npm run release`: bump de versão `patch` + deploy + publish do package (com auto commit/push git).
135
+ - `npm run release`: release completo com versão unificada (npmjs + GitHub Packages + GitHub Release) e verificação final.
136
136
  - `npm run release:minor`: bump `minor` + deploy + publish do package.
137
137
  - `npm run release:major`: bump `major` + deploy + publish do package.
138
138
  - `npm run test`: executa testes Node (`node --test`).
@@ -224,10 +224,14 @@ Comando único de release (patch + deploy + publish):
224
224
  npm run release
225
225
  ```
226
226
 
227
- `npm run release` agora executa publish primário + secundário por padrão (você pode desativar com `DEPLOY_PACKAGE_PUBLISH_SECONDARY=0`).
227
+ `npm run release` executa publish primário + secundário por padrão e valida consistência final de versão.
228
228
 
229
229
  Variáveis do fluxo de release (git):
230
230
 
231
+ - `RELEASE_TYPE` (default: `patch`)
232
+ - `RELEASE_FORCE_VERSION` (opcional) - força versão exata (ex.: `2.2.0`)
233
+ - `RELEASE_PATCH_ROLLOVER_ENABLED` (default: `1`) - rollover automático de patch
234
+ - `RELEASE_PATCH_ROLLOVER_AT` (default: `10`) - quando patch atingir este valor, próximo release vira `major.(minor+1).0`
231
235
  - `RELEASE_GIT_AUTO_COMMIT` (default: `1`) - commit automático se houver alterações pendentes
232
236
  - `RELEASE_GIT_AUTO_PUSH` (default: `1`) - push automático dos commits gerados
233
237
  - `RELEASE_GIT_REMOTE` (default: `origin`)
@@ -235,6 +239,26 @@ Variáveis do fluxo de release (git):
235
239
  - `RELEASE_GIT_PRE_COMMIT_MESSAGE` (default: `chore(release): auto-commit before release`)
236
240
  - `RELEASE_GIT_COMMIT_VERSION` (default: `1`) - commita alteração da versão após sucesso
237
241
  - `RELEASE_GIT_VERSION_COMMIT_PREFIX` (default: `chore(release): v`)
242
+ - `RELEASE_GITHUB_RELEASE` (default: `1`) - cria/atualiza GitHub Release na aba Releases
243
+ - `RELEASE_REQUIRE_GITHUB_RELEASE` (default: `1`) - falha se GitHub Release estiver desativado
244
+ - `RELEASE_GITHUB_REPO` (opcional; ex.: `kaikybrofc/omnizap-system`)
245
+ - `RELEASE_GITHUB_TOKEN` (opcional; fallback: `DEPLOY_GITHUB_TOKEN`/`GITHUB_TOKEN`/`GH_TOKEN`)
246
+ - `RELEASE_GITHUB_TAG_PREFIX` (default: `v`) - prefixo da tag (`v2.1.9`)
247
+ - `RELEASE_GITHUB_NAME_PREFIX` (default: `v`) - prefixo do nome exibido na release
248
+ - `RELEASE_GITHUB_GENERATE_NOTES` (default: `1`) - usa notas automáticas do GitHub
249
+ - `RELEASE_GITHUB_PRERELEASE` (default: auto) - vazio detecta `-` na versão como prerelease
250
+ - `RELEASE_GITHUB_DRAFT` (default: `0`)
251
+ - `RELEASE_GITHUB_TARGET` (opcional; vazio usa `HEAD`)
252
+ - `RELEASE_REQUIRE_DUAL_PUBLISH` (default: `1`) - exige publish em GitHub Packages e npmjs
253
+ - `RELEASE_VERIFY_UNIFIED_VERSION` (default: `1`) - valida versão final em local + registries + GitHub Release
254
+ - `RELEASE_VERIFY_PRIMARY_REGISTRY` (default: `https://npm.pkg.github.com`)
255
+ - `RELEASE_VERIFY_SECONDARY_REGISTRY` (default: `https://registry.npmjs.org`)
256
+ - `RELEASE_VERIFY_PRIMARY_TOKEN_KEYS` / `RELEASE_VERIFY_SECONDARY_TOKEN_KEYS` - ordem de fallback de tokens para verificação
257
+
258
+ Exemplo de rollover automático:
259
+
260
+ - `2.1.9` + `npm run release` -> `2.1.10`
261
+ - `2.1.10` + `npm run release` -> `2.2.0`
238
262
 
239
263
  ## Execução com PM2
240
264
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaikybrofc/omnizap-system",
3
- "version": "2.1.9",
3
+ "version": "2.2.0",
4
4
  "description": "Sistema profissional de automação WhatsApp com tecnologia Baileys",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+ import dotenv from 'dotenv';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const projectRoot = path.resolve(__dirname, '..');
12
+
13
+ dotenv.config({ path: path.join(projectRoot, '.env') });
14
+
15
+ const args = process.argv.slice(2);
16
+ const action = String(args[0] || '').trim();
17
+
18
+ const getArg = (flag, fallback = '') => {
19
+ const index = args.indexOf(flag);
20
+ if (index === -1) return fallback;
21
+ return String(args[index + 1] || fallback).trim();
22
+ };
23
+
24
+ const env = (...keys) => {
25
+ for (const key of keys) {
26
+ const value = process.env[key];
27
+ if (value && String(value).trim()) return String(value).trim();
28
+ }
29
+ return '';
30
+ };
31
+
32
+ const toBool = (value, fallback = false) => {
33
+ if (value === undefined || value === null || value === '') return fallback;
34
+ const normalized = String(value).trim().toLowerCase();
35
+ return ['1', 'true', 'yes', 'on'].includes(normalized);
36
+ };
37
+
38
+ const parseRepoFromRemote = () => {
39
+ try {
40
+ const remoteUrl = execSync('git config --get remote.origin.url', {
41
+ cwd: projectRoot,
42
+ stdio: ['ignore', 'pipe', 'ignore'],
43
+ })
44
+ .toString('utf8')
45
+ .trim();
46
+ if (!remoteUrl) return '';
47
+
48
+ const httpsMatch = remoteUrl.match(/github\.com\/([^/]+\/[^/.]+)(?:\.git)?$/i);
49
+ if (httpsMatch?.[1]) return httpsMatch[1];
50
+
51
+ const sshMatch = remoteUrl.match(/github\.com:([^/]+\/[^/.]+)(?:\.git)?$/i);
52
+ if (sshMatch?.[1]) return sshMatch[1];
53
+ } catch {
54
+ return '';
55
+ }
56
+ return '';
57
+ };
58
+
59
+ const token = env('RELEASE_GITHUB_TOKEN', 'DEPLOY_GITHUB_TOKEN', 'GITHUB_TOKEN', 'GH_TOKEN');
60
+ const repository = env('RELEASE_GITHUB_REPO', 'DEPLOY_GITHUB_REPO', 'GITHUB_REPOSITORY') || parseRepoFromRemote();
61
+
62
+ if (!action || !['upsert', 'get'].includes(action)) {
63
+ console.error('Uso: node scripts/github-release-notify.mjs <upsert|get> --tag vX.Y.Z [opções]');
64
+ process.exit(1);
65
+ }
66
+
67
+ if (!token || !repository) {
68
+ console.error('GitHub release notify ignorado: token ou repositório não configurado.');
69
+ process.exit(2);
70
+ }
71
+
72
+ const [repoOwnerRaw, repoNameRaw] = repository.split('/', 2);
73
+ const repoOwner = String(repoOwnerRaw || '').trim();
74
+ const repoName = String(repoNameRaw || '').trim();
75
+ if (!repoOwner || !repoName) {
76
+ console.error('GitHub release notify ignorado: formato de repositório inválido (esperado owner/repo).');
77
+ process.exit(2);
78
+ }
79
+
80
+ const tag = getArg('--tag');
81
+ const target = getArg('--target');
82
+ const name = getArg('--name', tag);
83
+ const body = getArg('--body', '');
84
+ const generateNotes = toBool(getArg('--generate-notes', 'true'), true);
85
+ const prerelease = toBool(getArg('--prerelease', 'false'), false);
86
+ const draft = toBool(getArg('--draft', 'false'), false);
87
+
88
+ if (!tag) {
89
+ console.error('Parâmetro obrigatório ausente: --tag');
90
+ process.exit(1);
91
+ }
92
+
93
+ const headers = {
94
+ Accept: 'application/vnd.github+json',
95
+ Authorization: `Bearer ${token}`,
96
+ 'X-GitHub-Api-Version': '2022-11-28',
97
+ 'Content-Type': 'application/json',
98
+ 'User-Agent': 'omnizap-release-script',
99
+ };
100
+
101
+ const request = async (url, method, payload) => {
102
+ const response = await fetch(url, {
103
+ method,
104
+ headers,
105
+ body: payload ? JSON.stringify(payload) : undefined,
106
+ });
107
+
108
+ const raw = await response.text();
109
+ let data = null;
110
+ try {
111
+ data = raw ? JSON.parse(raw) : null;
112
+ } catch {
113
+ data = null;
114
+ }
115
+
116
+ return {
117
+ ok: response.ok,
118
+ status: response.status,
119
+ data,
120
+ raw,
121
+ };
122
+ };
123
+
124
+ const failFromResponse = (response, fallbackPrefix = 'GitHub API') => {
125
+ const message = response?.data?.message || response?.raw || 'unknown error';
126
+ throw new Error(`${fallbackPrefix} ${response?.status ?? 'n/a'}: ${message}`);
127
+ };
128
+
129
+ const run = async () => {
130
+ const baseUrl = `https://api.github.com/repos/${encodeURIComponent(repoOwner)}/${encodeURIComponent(repoName)}`;
131
+
132
+ const byTag = await request(`${baseUrl}/releases/tags/${encodeURIComponent(tag)}`, 'GET');
133
+ let existingRelease = null;
134
+ if (byTag.ok) {
135
+ existingRelease = byTag.data;
136
+ } else if (byTag.status !== 404) {
137
+ failFromResponse(byTag, 'GitHub API');
138
+ }
139
+
140
+ if (action === 'get') {
141
+ if (!existingRelease) {
142
+ throw new Error(`GitHub release não encontrada para tag ${tag}`);
143
+ }
144
+ const url = existingRelease.html_url || '';
145
+ process.stdout.write(`found id=${existingRelease.id} tag=${tag} url=${url}`);
146
+ return;
147
+ }
148
+
149
+ const commonPayload = {
150
+ tag_name: tag,
151
+ target_commitish: target || undefined,
152
+ name: name || tag,
153
+ draft,
154
+ prerelease,
155
+ };
156
+
157
+ if (body) {
158
+ commonPayload.body = body;
159
+ }
160
+
161
+ if (existingRelease) {
162
+ const update = await request(`${baseUrl}/releases/${existingRelease.id}`, 'PATCH', commonPayload);
163
+ if (!update.ok) {
164
+ failFromResponse(update, 'GitHub API');
165
+ }
166
+ const url = update.data?.html_url || '';
167
+ process.stdout.write(`updated id=${update.data?.id} tag=${tag} url=${url}`);
168
+ return;
169
+ }
170
+
171
+ const createPayload = { ...commonPayload };
172
+ if (generateNotes) {
173
+ createPayload.generate_release_notes = true;
174
+ }
175
+
176
+ const created = await request(`${baseUrl}/releases`, 'POST', createPayload);
177
+ if (!created.ok) {
178
+ failFromResponse(created, 'GitHub API');
179
+ }
180
+
181
+ const url = created.data?.html_url || '';
182
+ process.stdout.write(`created id=${created.data?.id} tag=${tag} url=${url}`);
183
+ };
184
+
185
+ run().catch((error) => {
186
+ console.error(error?.message || error);
187
+ process.exit(1);
188
+ });
@@ -3,6 +3,9 @@ set -euo pipefail
3
3
 
4
4
  PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  RELEASE_TYPE="${RELEASE_TYPE:-patch}"
6
+ RELEASE_FORCE_VERSION="${RELEASE_FORCE_VERSION:-}"
7
+ RELEASE_PATCH_ROLLOVER_ENABLED="${RELEASE_PATCH_ROLLOVER_ENABLED:-1}"
8
+ RELEASE_PATCH_ROLLOVER_AT="${RELEASE_PATCH_ROLLOVER_AT:-10}"
6
9
  RELEASE_GIT_AUTO_COMMIT="${RELEASE_GIT_AUTO_COMMIT:-1}"
7
10
  RELEASE_GIT_AUTO_PUSH="${RELEASE_GIT_AUTO_PUSH:-1}"
8
11
  RELEASE_GIT_REMOTE="${RELEASE_GIT_REMOTE:-origin}"
@@ -10,6 +13,21 @@ RELEASE_GIT_BRANCH="${RELEASE_GIT_BRANCH:-}"
10
13
  RELEASE_GIT_PRE_COMMIT_MESSAGE="${RELEASE_GIT_PRE_COMMIT_MESSAGE:-chore(release): auto-commit before release}"
11
14
  RELEASE_GIT_COMMIT_VERSION="${RELEASE_GIT_COMMIT_VERSION:-1}"
12
15
  RELEASE_GIT_VERSION_COMMIT_PREFIX="${RELEASE_GIT_VERSION_COMMIT_PREFIX:-chore(release): v}"
16
+ RELEASE_GITHUB_RELEASE="${RELEASE_GITHUB_RELEASE:-1}"
17
+ RELEASE_REQUIRE_GITHUB_RELEASE="${RELEASE_REQUIRE_GITHUB_RELEASE:-1}"
18
+ RELEASE_GITHUB_TAG_PREFIX="${RELEASE_GITHUB_TAG_PREFIX:-v}"
19
+ RELEASE_GITHUB_NAME_PREFIX="${RELEASE_GITHUB_NAME_PREFIX:-v}"
20
+ RELEASE_GITHUB_GENERATE_NOTES="${RELEASE_GITHUB_GENERATE_NOTES:-1}"
21
+ RELEASE_GITHUB_PRERELEASE="${RELEASE_GITHUB_PRERELEASE:-}"
22
+ RELEASE_GITHUB_DRAFT="${RELEASE_GITHUB_DRAFT:-0}"
23
+ RELEASE_GITHUB_TARGET="${RELEASE_GITHUB_TARGET:-}"
24
+ RELEASE_REQUIRE_DUAL_PUBLISH="${RELEASE_REQUIRE_DUAL_PUBLISH:-1}"
25
+ RELEASE_VERIFY_UNIFIED_VERSION="${RELEASE_VERIFY_UNIFIED_VERSION:-1}"
26
+ RELEASE_VERIFY_PRIMARY_REGISTRY="${RELEASE_VERIFY_PRIMARY_REGISTRY:-${DEPLOY_PACKAGE_REGISTRY:-https://npm.pkg.github.com}}"
27
+ RELEASE_VERIFY_SECONDARY_REGISTRY="${RELEASE_VERIFY_SECONDARY_REGISTRY:-${DEPLOY_PACKAGE_SECONDARY_REGISTRY:-https://registry.npmjs.org}}"
28
+ RELEASE_VERIFY_PRIMARY_TOKEN_KEYS="${RELEASE_VERIFY_PRIMARY_TOKEN_KEYS:-DEPLOY_PACKAGE_TOKEN,DEPLOY_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN}"
29
+ RELEASE_VERIFY_SECONDARY_TOKEN_KEYS="${RELEASE_VERIFY_SECONDARY_TOKEN_KEYS:-DEPLOY_PACKAGE_SECONDARY_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN}"
30
+ TMP_NPMRC_FILES=()
13
31
 
14
32
  case "$RELEASE_TYPE" in
15
33
  patch|minor|major|prepatch|preminor|premajor|prerelease)
@@ -25,6 +43,15 @@ log() {
25
43
  printf '[release] %s\n' "$*"
26
44
  }
27
45
 
46
+ cleanup_tmp_npmrcs() {
47
+ for npmrc_tmp in "${TMP_NPMRC_FILES[@]:-}"; do
48
+ if [ -n "$npmrc_tmp" ] && [ -f "$npmrc_tmp" ]; then
49
+ rm -f "$npmrc_tmp"
50
+ fi
51
+ done
52
+ }
53
+ trap cleanup_tmp_npmrcs EXIT
54
+
28
55
  require_cmd() {
29
56
  if ! command -v "$1" >/dev/null 2>&1; then
30
57
  printf '[release] comando ausente: %s\n' "$1" >&2
@@ -32,6 +59,23 @@ require_cmd() {
32
59
  fi
33
60
  }
34
61
 
62
+ to_bool() {
63
+ local value
64
+ value="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
65
+ case "$value" in
66
+ 1|true|yes|on)
67
+ printf 'true'
68
+ ;;
69
+ *)
70
+ printf 'false'
71
+ ;;
72
+ esac
73
+ }
74
+
75
+ sanitize_npm_output() {
76
+ printf '%s' "$1" | tr -d "\"'[:space:]"
77
+ }
78
+
35
79
  resolve_branch() {
36
80
  if [ -n "$RELEASE_GIT_BRANCH" ]; then
37
81
  printf '%s' "$RELEASE_GIT_BRANCH"
@@ -49,6 +93,130 @@ resolve_branch() {
49
93
  printf '%s' "$branch"
50
94
  }
51
95
 
96
+ resolve_token_from_dotenv() {
97
+ local token_keys="$1"
98
+ if [ -z "$token_keys" ]; then
99
+ return 0
100
+ fi
101
+
102
+ (
103
+ cd "$PROJECT_ROOT" && TOKEN_KEYS="$token_keys" node --input-type=module -e "
104
+ import dotenv from 'dotenv';
105
+ dotenv.config({ path: '.env' });
106
+ const keys = String(process.env.TOKEN_KEYS || '')
107
+ .split(',')
108
+ .map((item) => item.trim())
109
+ .filter(Boolean);
110
+ for (const key of keys) {
111
+ const value = process.env[key];
112
+ if (value && String(value).trim()) {
113
+ process.stdout.write(String(value).trim());
114
+ process.exit(0);
115
+ }
116
+ }
117
+ " 2>/dev/null || true
118
+ )
119
+ }
120
+
121
+ create_npmrc_for_registry() {
122
+ local registry="$1"
123
+ local token="$2"
124
+ local scope_owner="$3"
125
+ local registry_host=""
126
+ registry_host="$(printf '%s' "$registry" | sed -E 's#^https?://##; s#/*$##')"
127
+
128
+ local npmrc_tmp=""
129
+ npmrc_tmp="$(mktemp /tmp/omnizap-release-npmrc.XXXXXX)"
130
+ {
131
+ printf 'registry=%s\n' "$registry"
132
+ if [ -n "$scope_owner" ]; then
133
+ printf '@%s:registry=%s\n' "$scope_owner" "$registry"
134
+ fi
135
+ if [ -n "$token" ]; then
136
+ printf '//%s/:_authToken=%s\n' "$registry_host" "$token"
137
+ printf '//%s:_authToken=%s\n' "$registry_host" "$token"
138
+ fi
139
+ } > "$npmrc_tmp"
140
+ chmod 600 "$npmrc_tmp"
141
+ TMP_NPMRC_FILES+=("$npmrc_tmp")
142
+ printf '%s' "$npmrc_tmp"
143
+ }
144
+
145
+ verify_registry_version() {
146
+ local pkg_name="$1"
147
+ local pkg_version="$2"
148
+ local registry="$3"
149
+ local token_keys="$4"
150
+ local auth_required="$5"
151
+
152
+ local token=""
153
+ token="$(resolve_token_from_dotenv "$token_keys")"
154
+ if [ "$auth_required" = "1" ] && [ -z "$token" ]; then
155
+ printf '[release] Verificação em %s requer token (keys: %s).\n' "$registry" "$token_keys" >&2
156
+ exit 1
157
+ fi
158
+
159
+ local scope_owner=""
160
+ scope_owner="$(printf '%s' "$pkg_name" | sed -nE 's#^@([^/]+)/.*#\1#p')"
161
+ local npmrc_tmp=""
162
+ npmrc_tmp="$(create_npmrc_for_registry "$registry" "$token" "$scope_owner")"
163
+
164
+ local version_raw=""
165
+ if ! version_raw="$(
166
+ cd "$PROJECT_ROOT" &&
167
+ npm_config_userconfig="$npmrc_tmp" npm view "${pkg_name}@${pkg_version}" version --registry "$registry" --userconfig "$npmrc_tmp" 2>/dev/null
168
+ )"; then
169
+ printf '[release] Falha ao validar versão %s em %s.\n' "$pkg_version" "$registry" >&2
170
+ exit 1
171
+ fi
172
+ local version_value=""
173
+ version_value="$(sanitize_npm_output "$version_raw")"
174
+ if [ "$version_value" != "$pkg_version" ]; then
175
+ printf '[release] Versão divergente em %s: esperado=%s encontrado=%s\n' "$registry" "$pkg_version" "${version_value:-vazio}" >&2
176
+ exit 1
177
+ fi
178
+
179
+ local latest_raw=""
180
+ if ! latest_raw="$(
181
+ cd "$PROJECT_ROOT" &&
182
+ npm_config_userconfig="$npmrc_tmp" npm view "$pkg_name" dist-tags.latest --registry "$registry" --userconfig "$npmrc_tmp" 2>/dev/null
183
+ )"; then
184
+ printf '[release] Falha ao validar dist-tag latest em %s.\n' "$registry" >&2
185
+ exit 1
186
+ fi
187
+ local latest_value=""
188
+ latest_value="$(sanitize_npm_output "$latest_raw")"
189
+ if [ "$latest_value" != "$pkg_version" ]; then
190
+ printf '[release] Dist-tag latest divergente em %s: esperado=%s latest=%s\n' "$registry" "$pkg_version" "${latest_value:-vazio}" >&2
191
+ exit 1
192
+ fi
193
+
194
+ log "Verificado em $registry: versão=$pkg_version latest=$latest_value"
195
+ }
196
+
197
+ compute_target_version() {
198
+ local current="$1"
199
+
200
+ if [ -n "$RELEASE_FORCE_VERSION" ]; then
201
+ printf '%s' "$RELEASE_FORCE_VERSION"
202
+ return 0
203
+ fi
204
+
205
+ if [ "$RELEASE_TYPE" = "patch" ] && [ "$RELEASE_PATCH_ROLLOVER_ENABLED" = "1" ]; then
206
+ if [[ "$RELEASE_PATCH_ROLLOVER_AT" =~ ^[0-9]+$ ]] && [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
207
+ local major="${BASH_REMATCH[1]}"
208
+ local minor="${BASH_REMATCH[2]}"
209
+ local patch="${BASH_REMATCH[3]}"
210
+ if [ "$patch" -ge "$RELEASE_PATCH_ROLLOVER_AT" ]; then
211
+ printf '%s.%s.0' "$major" "$((minor + 1))"
212
+ return 0
213
+ fi
214
+ fi
215
+ fi
216
+
217
+ printf ''
218
+ }
219
+
52
220
  commit_and_push_if_dirty() {
53
221
  local commit_message="$1"
54
222
 
@@ -86,30 +254,55 @@ commit_and_push_if_dirty() {
86
254
 
87
255
  require_cmd git
88
256
  require_cmd npm
257
+ require_cmd node
89
258
 
90
259
  if ! (cd "$PROJECT_ROOT" && git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
91
260
  printf '[release] este diretório não é um repositório git válido.\n' >&2
92
261
  exit 1
93
262
  fi
94
263
 
264
+ if [ "$RELEASE_REQUIRE_GITHUB_RELEASE" = "1" ] && [ "$RELEASE_GITHUB_RELEASE" != "1" ]; then
265
+ printf '[release] RELEASE_REQUIRE_GITHUB_RELEASE=1 exige RELEASE_GITHUB_RELEASE=1.\n' >&2
266
+ exit 1
267
+ fi
268
+
95
269
  commit_and_push_if_dirty "$RELEASE_GIT_PRE_COMMIT_MESSAGE"
96
270
 
97
271
  current_version="$(cd "$PROJECT_ROOT" && npm pkg get version | tr -d '"[:space:]')"
98
272
  log "Versão atual: $current_version"
99
- log "Aplicando bump: $RELEASE_TYPE"
273
+ target_version="$(compute_target_version "$current_version")"
100
274
 
101
- (cd "$PROJECT_ROOT" && npm version "$RELEASE_TYPE" --no-git-tag-version >/dev/null)
275
+ if [ -n "$target_version" ]; then
276
+ if [ "$target_version" = "$current_version" ]; then
277
+ printf '[release] versão alvo igual à versão atual (%s). Verifique regras de bump.\n' "$current_version" >&2
278
+ exit 1
279
+ fi
280
+ log "Aplicando versão alvo: $target_version"
281
+ (cd "$PROJECT_ROOT" && npm version "$target_version" --no-git-tag-version >/dev/null)
282
+ else
283
+ log "Aplicando bump: $RELEASE_TYPE"
284
+ (cd "$PROJECT_ROOT" && npm version "$RELEASE_TYPE" --no-git-tag-version >/dev/null)
285
+ fi
102
286
 
103
287
  new_version="$(cd "$PROJECT_ROOT" && npm pkg get version | tr -d '"[:space:]')"
104
288
  log "Nova versão: $new_version"
105
289
 
106
290
  log "Executando deploy com publish de package"
291
+ deploy_publish_primary="${DEPLOY_PACKAGE_PUBLISH:-1}"
292
+ deploy_publish_secondary="${DEPLOY_PACKAGE_PUBLISH_SECONDARY:-1}"
293
+
294
+ if [ "$RELEASE_REQUIRE_DUAL_PUBLISH" = "1" ]; then
295
+ if [ "$deploy_publish_primary" != "1" ] || [ "$deploy_publish_secondary" != "1" ]; then
296
+ printf '[release] RELEASE_REQUIRE_DUAL_PUBLISH=1 exige DEPLOY_PACKAGE_PUBLISH=1 e DEPLOY_PACKAGE_PUBLISH_SECONDARY=1.\n' >&2
297
+ exit 1
298
+ fi
299
+ fi
107
300
 
108
301
  if ! (
109
302
  cd "$PROJECT_ROOT"
110
303
  DEPLOY_PACKAGE_STEP="${DEPLOY_PACKAGE_STEP:-1}" \
111
- DEPLOY_PACKAGE_PUBLISH="${DEPLOY_PACKAGE_PUBLISH:-1}" \
112
- DEPLOY_PACKAGE_PUBLISH_SECONDARY="${DEPLOY_PACKAGE_PUBLISH_SECONDARY:-1}" \
304
+ DEPLOY_PACKAGE_PUBLISH="$deploy_publish_primary" \
305
+ DEPLOY_PACKAGE_PUBLISH_SECONDARY="$deploy_publish_secondary" \
113
306
  DEPLOY_PACKAGE_SECONDARY_REGISTRY="${DEPLOY_PACKAGE_SECONDARY_REGISTRY:-https://registry.npmjs.org}" \
114
307
  DEPLOY_PACKAGE_SECONDARY_ACCESS="${DEPLOY_PACKAGE_SECONDARY_ACCESS:-public}" \
115
308
  DEPLOY_PACKAGE_INSTALL="${DEPLOY_PACKAGE_INSTALL:-0}" \
@@ -126,4 +319,71 @@ if [ "$RELEASE_GIT_COMMIT_VERSION" = "1" ]; then
126
319
  commit_and_push_if_dirty "${RELEASE_GIT_VERSION_COMMIT_PREFIX}${new_version}"
127
320
  fi
128
321
 
322
+ release_tag="${RELEASE_GITHUB_TAG_PREFIX}${new_version}"
323
+
324
+ if [ "$RELEASE_GITHUB_RELEASE" = "1" ]; then
325
+ if [ "$RELEASE_GIT_AUTO_PUSH" != "1" ]; then
326
+ printf '[release] RELEASE_GITHUB_RELEASE=1 requer RELEASE_GIT_AUTO_PUSH=1 para garantir commit acessível no GitHub.\n' >&2
327
+ exit 1
328
+ fi
329
+
330
+ local_name="${RELEASE_GITHUB_NAME_PREFIX}${new_version}"
331
+ local_target="$RELEASE_GITHUB_TARGET"
332
+ if [ -z "$local_target" ]; then
333
+ local_target="$(cd "$PROJECT_ROOT" && git rev-parse HEAD)"
334
+ fi
335
+
336
+ local_prerelease="$RELEASE_GITHUB_PRERELEASE"
337
+ if [ -z "$local_prerelease" ]; then
338
+ if printf '%s' "$new_version" | grep -q '-'; then
339
+ local_prerelease="1"
340
+ else
341
+ local_prerelease="0"
342
+ fi
343
+ fi
344
+
345
+ local generate_notes_bool=""
346
+ generate_notes_bool="$(to_bool "$RELEASE_GITHUB_GENERATE_NOTES")"
347
+ local prerelease_bool=""
348
+ prerelease_bool="$(to_bool "$local_prerelease")"
349
+ local draft_bool=""
350
+ draft_bool="$(to_bool "$RELEASE_GITHUB_DRAFT")"
351
+
352
+ log "Criando/atualizando GitHub Release ($release_tag)"
353
+ release_output="$(
354
+ cd "$PROJECT_ROOT" && node ./scripts/github-release-notify.mjs upsert \
355
+ --tag "$release_tag" \
356
+ --target "$local_target" \
357
+ --name "$local_name" \
358
+ --generate-notes "$generate_notes_bool" \
359
+ --prerelease "$prerelease_bool" \
360
+ --draft "$draft_bool"
361
+ )"
362
+ log "GitHub Release atualizado: $release_output"
363
+ fi
364
+
365
+ if [ "$RELEASE_VERIFY_UNIFIED_VERSION" = "1" ]; then
366
+ pkg_name="$(cd "$PROJECT_ROOT" && npm pkg get name | tr -d '"[:space:]')"
367
+ if [ -z "$pkg_name" ]; then
368
+ printf '[release] Falha ao ler nome do pacote para verificação final.\n' >&2
369
+ exit 1
370
+ fi
371
+
372
+ local_version_now="$(cd "$PROJECT_ROOT" && npm pkg get version | tr -d '"[:space:]')"
373
+ if [ "$local_version_now" != "$new_version" ]; then
374
+ printf '[release] Versão local divergente após release: esperado=%s encontrado=%s\n' "$new_version" "$local_version_now" >&2
375
+ exit 1
376
+ fi
377
+ log "Verificado localmente: versão=$local_version_now"
378
+
379
+ verify_registry_version "$pkg_name" "$new_version" "$RELEASE_VERIFY_PRIMARY_REGISTRY" "$RELEASE_VERIFY_PRIMARY_TOKEN_KEYS" "1"
380
+ verify_registry_version "$pkg_name" "$new_version" "$RELEASE_VERIFY_SECONDARY_REGISTRY" "$RELEASE_VERIFY_SECONDARY_TOKEN_KEYS" "0"
381
+
382
+ gh_release_check="$(
383
+ cd "$PROJECT_ROOT" && node ./scripts/github-release-notify.mjs get --tag "$release_tag"
384
+ )"
385
+ log "Verificado GitHub Release: $gh_release_check"
386
+ log "Verificação final concluída: todas as versões estão em $new_version"
387
+ fi
388
+
129
389
  log "Release concluída: $new_version"