parche-cli 0.6.8 → 0.6.9

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  Los cambios de cada versión de `parche-cli` y de la Action `Pepo08/parche@v0`. En npm están la 0.1.1, la 0.5.1 y de la 0.6.0 en adelante. La 0.1.0 y de la 0.2.0 a la 0.5.0 no se publicaron en npm; lo que traían está incluido en la 0.5.1. La fecha de cada versión es la del cambio; la de publicación figura en npm y en los [Releases](https://github.com/Pepo08/parche/releases).
4
4
 
5
+ ## 0.6.9 — 2026-10-23
6
+
7
+ La Action muestra lo que no abre un PR, y la plantilla del workflow anda en repos PHP. Salió de probar cada comando sobre los ejemplos antes de mandar los primeros mensajes de venta.
8
+
9
+ ### Nuevo
10
+ - **Resumen del job y anotaciones en GitHub Actions:** `parche pr` escribe en el resumen de la corrida (`$GITHUB_STEP_SUMMARY`) los PRs que abrió, lo que quedó sin PR (por ejemplo, una factura A a un CUIT, donde falta la condición frente al IVA del cliente) y los avisos de los packs de solo detección, como un modelo de IA que se retira. Las primeras 10 también salen como anotaciones con el archivo y la línea. Hasta la 0.6.8, `pr` salteaba los packs sin arreglo y la Action no mostraba los avisos de IA.
11
+ - **Fuera de GitHub Actions,** `parche pr` imprime al final los avisos de solo detección ("Avisos sin PR").
12
+
13
+ ### Cambios
14
+ - **Plantilla del workflow (`templates/github-workflow.yml`):** `npm ci` corre solo si hay `package-lock.json`, y `composer install` solo si hay `composer.json`. Antes, en un repo PHP el workflow fallaba en el primer paso.
15
+ - **Inventario en PHP y Python:** un SDK declarado en `composer.json` o `requirements.txt` dice "los usos en PHP (o Python) todavía no se analizan", en vez de "sin usos encontrados", que sonaba a que no se usa.
16
+ - La CI prueba la Action también sobre un repo PHP (copia de `examples/tienda-php`).
17
+
5
18
  ## 0.6.8 — 2026-10-22
6
19
 
7
20
  Pack de IA: los alias de Claude que apuntan a un modelo con fecha. Antes de la generación 4.6, la API de Claude acepta alias cortos que apuntan al snapshot fechado más reciente de cada versión (página "Model IDs and versioning", verificada el 22/10/2026). La 0.6.7 avisaba `claude-sonnet-4-5-20250929`, que se retira el 30/11/2026, pero no `claude-sonnet-4-5`, que es el mismo modelo.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Joaquín González
3
+ Copyright (c) 2026 Joaquin Gonzalez Menza
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -91,7 +91,9 @@ Para que corra todas las semanas, copiá [`templates/github-workflow.yml`](templ
91
91
  # dry-run: 'true' # opcional: armar las ramas sin abrir PRs
92
92
  ```
93
93
 
94
- El workflow necesita `permissions: contents: write` y `pull-requests: write`.
94
+ El workflow necesita `permissions: contents: write` y `pull-requests: write`. Antes de Parche, la plantilla instala las dependencias de tu proyecto para que corran sus tests: `npm ci` si hay `package-lock.json` y `composer install` si hay `composer.json` (Composer viene en los runners de Ubuntu de GitHub). Así anda igual en un repo de Node y en uno de PHP.
95
+
96
+ Lo que no abre un PR también se ve en la corrida: los avisos de solo detección (modelos de IA que se retiran, por ejemplo) y lo que quedó sin PR porque falta un dato de tu negocio aparecen en el **resumen del job** y como **anotaciones** con el archivo y la línea (GitHub muestra las primeras 10; el resumen las tiene todas).
95
97
 
96
98
  Con el `GITHUB_TOKEN` por defecto, GitHub deja los workflows del PR que abre Parche esperando aprobación: alguien con permiso de escritura tiene que tocar **Approve workflows to run** (el cuerpo del PR lo avisa). Para que tu CI corra sola sobre esos PRs, pasale a la Action un token de una GitHub App o un token personal con permiso de escritura en `contents` y `pull-requests`:
97
99
 
package/bin/parche.js CHANGED
@@ -9,8 +9,9 @@ import { verify } from '../src/verify.js';
9
9
  import * as git from '../src/git.js';
10
10
  import { parseGithubRemote, openPullRequest } from '../src/github.js';
11
11
  import { record } from '../src/ledger.js';
12
- import { apiLabel } from '../src/inventory/index.js';
12
+ import { apiLabel, noUsesLabel } from '../src/inventory/index.js';
13
13
  import { isTestFile } from '../src/paths.js';
14
+ import { stepSummary, annotations, byPack, workspacePrefix } from '../src/actions.js';
14
15
 
15
16
  const HELP = `parche — Dependabot para APIs
16
17
 
@@ -67,7 +68,7 @@ function printInventory(inventory) {
67
68
  const ops = api.operations.map((o) => (o.count > 1 ? `${o.op} ×${o.count}` : o.op));
68
69
  const shown = ops.length > 4 ? [...ops.slice(0, 4), `+${ops.length - 4} más`] : ops;
69
70
  const packs = api.packs.length ? ' · con pack' : '';
70
- console.log(` ${KIND[api.kind]} ${apiLabel(api).padEnd(38)} ${api.confidence.padEnd(5)} ${shown.join(', ') || '(declarada, sin usos encontrados)'}${packs}`);
71
+ console.log(` ${KIND[api.kind]} ${apiLabel(api).padEnd(38)} ${api.confidence.padEnd(5)} ${shown.join(', ') || `(${noUsesLabel(api)})`}${packs}`);
71
72
  }
72
73
  if (inventory.unresolved.length) {
73
74
  console.log(` + ${inventory.unresolved.length} llamada(s) HTTP con URL dinámica (no se sabe a qué host van; detalle en el reporte)`);
@@ -225,6 +226,9 @@ async function main() {
225
226
  const packs = loadPacks().filter((p) => p.codemod && (!packIds || packIds.includes(p.id)));
226
227
  // A dónde se pushea: origin, o el fork de quien abre el PR (--push-remote fork). El PR se abre contra origin.
227
228
  const pushRemote = typeof args['push-remote'] === 'string' ? args['push-remote'] : 'origin';
229
+ // Para el resumen del job en GitHub Actions: los PRs de esta corrida y lo que quedó sin PR.
230
+ const opened = [];
231
+ const withoutPr = [];
228
232
  for (const pack of packs) {
229
233
  const preview = run(root, { write: false, packIds: [pack.id] });
230
234
  if (!preview.findings.length) continue;
@@ -258,6 +262,7 @@ async function main() {
258
262
  console.log(` ${summary(result.findings)}`);
259
263
  console.log(` ${why[0].toUpperCase()}${why.slice(1)}; queda como issue o reporte: ${bodyPath}`);
260
264
  record(root, { type: 'report-only', pack: pack.id, findings: c, reason: why });
265
+ withoutPr.push({ pack, findings: result.findings });
261
266
  continue;
262
267
  }
263
268
  const draft = !!(c['needs-review'] || c['needs-input'] || c.blocked);
@@ -277,6 +282,7 @@ async function main() {
277
282
  } else {
278
283
  console.log(` Modo prueba: no se abrió el PR${draft ? ' (iría como borrador por tener ítems para revisar)' : ''}.`);
279
284
  }
285
+ opened.push({ title, url, draft });
280
286
  record(root, { type: 'pr', pack: pack.id, packVersion: pack.version, branch, sha, url, draft, files: changedFiles, findings: c,
281
287
  partial: result.findings.filter((f) => f.partial).length });
282
288
  } finally {
@@ -285,6 +291,19 @@ async function main() {
285
291
  if (!keepBranch) git.deleteBranch(root, branch);
286
292
  }
287
293
  }
294
+ // Los packs de solo detección (IA) no abren PR: sus avisos se muestran acá y, en GitHub Actions, en el resumen del
295
+ // job y como anotaciones, junto con lo que quedó sin PR. Antes `pr` los salteaba y en la Action no se veían (D67).
296
+ const detectPacks = loadPacks().filter((p) => !p.codemod && (!packIds || packIds.includes(p.id)));
297
+ const detected = detectPacks.length ? run(root, { write: false, packIds: detectPacks.map((p) => p.id) }) : { packs: [], findings: [] };
298
+ const notices = byPack(detected);
299
+ if (notices.length) {
300
+ console.log('\nAvisos sin PR (solo detección):');
301
+ printFindings(detected);
302
+ }
303
+ if (process.env.GITHUB_ACTIONS === 'true') {
304
+ if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, stepSummary({ opened, reportOnly: withoutPr, notices }));
305
+ for (const line of annotations([...notices, ...withoutPr], workspacePrefix(root, process.env.GITHUB_WORKSPACE))) console.log(line);
306
+ }
288
307
  return 0;
289
308
  }
290
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parche-cli",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "Dependabot para APIs: detecta cambios de cualquier API de terceros en tu código (ARCA, Mercado Pago, OpenAI, Anthropic) y abre el PR con el arreglo.",
5
5
  "keywords": [
6
6
  "api",
@@ -25,7 +25,7 @@
25
25
  "type": "git",
26
26
  "url": "git+https://github.com/Pepo08/parche.git"
27
27
  },
28
- "author": "Joaquín González",
28
+ "author": "Joaquin Gonzalez Menza",
29
29
  "license": "MIT",
30
30
  "type": "module",
31
31
  "bin": {
package/src/actions.js ADDED
@@ -0,0 +1,72 @@
1
+ // Lo que se ve en GitHub Actions además de los PRs: el resumen del job ($GITHUB_STEP_SUMMARY) y las anotaciones
2
+ // (::warning) con archivo y línea. Sin esto, los avisos de los packs de solo detección (IA) y lo que queda sin PR
3
+ // (falta un dato, o no hay código para cambiar) se perdían dentro del runner (sesión del 23/10, D67).
4
+ import path from 'node:path';
5
+ import { LABEL } from './report.js';
6
+
7
+ // Comandos de workflow de GitHub: el mensaje escapa %, \r y \n; las propiedades (file, title), además, ":" y ",".
8
+ export const escapeData = (s) => String(s).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
9
+ export const escapeProperty = (s) => escapeData(s).replace(/:/g, '%3A').replace(/,/g, '%2C');
10
+
11
+ // GitHub muestra hasta 10 anotaciones de advertencia por paso: las primeras van como anotación y todas en el resumen.
12
+ export const MAX_ANNOTATIONS = 10;
13
+
14
+ // Grupos { pack, findings } de un resultado del motor, en el orden de los packs y sin los packs vacíos.
15
+ export const byPack = (result) => result.packs
16
+ .map((pack) => ({ pack, findings: result.findings.filter((f) => f.pack === pack.id) }))
17
+ .filter((g) => g.findings.length);
18
+
19
+ // Prefijo para que la ruta del hallazgo (relativa a la carpeta que se revisó) sea relativa a la raíz del checkout,
20
+ // que es lo que espera una anotación. Si la carpeta está fuera del checkout, no hay prefijo válido: null.
21
+ export function workspacePrefix(root, workspace) {
22
+ if (!workspace) return '';
23
+ const rel = path.relative(path.resolve(workspace), path.resolve(root));
24
+ if (rel === '') return '';
25
+ if (rel.startsWith('..') || path.isAbsolute(rel)) return null;
26
+ return `${rel.split(path.sep).join('/')}/`;
27
+ }
28
+
29
+ // Una anotación por hallazgo, hasta MAX_ANNOTATIONS. Sin prefijo válido, el archivo y la línea van en el mensaje.
30
+ export function annotations(groups, prefix = '') {
31
+ const out = [];
32
+ for (const { pack, findings } of groups) {
33
+ for (const f of findings) {
34
+ if (out.length === MAX_ANNOTATIONS) return out;
35
+ const where = `${f.file}${f.line ? `:${f.line}` : ''}`;
36
+ const props = prefix === null ? [] : [`file=${escapeProperty(prefix + f.file)}`, ...(f.line ? [`line=${f.line}`] : [])];
37
+ props.push(`title=${escapeProperty(`Parche · ${pack.title}`)}`);
38
+ out.push(`::warning ${props.join(',')}::${escapeData(prefix === null ? `${where}: ${f.message}` : f.message)}`);
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+
44
+ // El resumen del job: los PRs de esta corrida, lo que quedó sin PR y los avisos de solo detección.
45
+ // opened: [{ title, url, draft }] (url null en modo prueba); reportOnly y notices: grupos { pack, findings }.
46
+ export function stepSummary({ opened = [], reportOnly = [], notices = [] }) {
47
+ const lines = ['## Parche', ''];
48
+ if (opened.length) {
49
+ lines.push('### PRs', '');
50
+ for (const o of opened) {
51
+ const draft = o.draft ? ' · borrador' : '';
52
+ lines.push(o.url ? `- [${o.title}](${o.url})${draft}` : `- ${o.title}${draft} · modo prueba: no se abrió`);
53
+ }
54
+ lines.push('');
55
+ }
56
+ const section = (title, intro, groups) => {
57
+ if (!groups.length) return;
58
+ lines.push(`### ${title}`, '', intro, '');
59
+ for (const { pack, findings } of groups) {
60
+ lines.push(`**${pack.title}** · ${pack.provider}`, '');
61
+ for (const f of findings) lines.push(`- ${LABEL[f.status]} · \`${f.file}${f.line ? `:${f.line}` : ''}\` · ${f.message}`);
62
+ lines.push('');
63
+ }
64
+ };
65
+ section('Sin PR', 'Parche no abre un PR cuando el arreglo depende de un dato de tu negocio o cuando no hay código para cambiar. Esto es lo que encontró y lo que falta.', reportOnly);
66
+ section('Avisos', 'Cambios con fecha que afectan a tu código. Parche no los cambia solo: cambiar de modelo, por ejemplo, cambia cómo responde tu producto.', notices);
67
+ if (!opened.length && !reportOnly.length && !notices.length) {
68
+ lines.push('Ninguno de los packs verificados tiene cambios que te afecten hoy.', '');
69
+ }
70
+ lines.push('El inventario completo de las APIs del repo: `npx parche-cli scan .`');
71
+ return `${lines.join('\n')}\n`;
72
+ }
@@ -126,6 +126,14 @@ export function buildInventory({ repo, manifests, fileScans, packs }) {
126
126
  }
127
127
 
128
128
  // Nombre corto para mostrar una API en el reporte y en la CLI.
129
+ // Qué decir de un SDK sin operaciones: en JS/TS se buscaron los usos y no aparecieron; en PHP y Python los usos todavía
130
+ // no se buscan (solo se lee el manifiesto), y "sin usos encontrados" sonaba a que no se usa (sesión del 23/10).
131
+ const NOT_SCANNED = { composer: ['composer.json', 'PHP'], pypi: ['requirements.txt', 'Python'] };
132
+ export function noUsesLabel(api) {
133
+ const ns = api.kind === 'sdk' && NOT_SCANNED[api.ecosystem];
134
+ return ns ? `declarada en ${ns[0]}; los usos en ${ns[1]} todavía no se analizan` : 'declarada, sin usos encontrados';
135
+ }
136
+
129
137
  export function apiLabel(api) {
130
138
  if (api.kind === 'sdk') return api.provider ? `${api.provider} · ${api.package}` : api.package;
131
139
  if (api.kind === 'http') return api.provider ? `${api.provider} · ${api.host}` : api.host;
package/src/report.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Reporte del scan y cuerpo del PR, en castellano.
2
- import { apiLabel } from './inventory/index.js';
2
+ import { apiLabel, noUsesLabel } from './inventory/index.js';
3
3
 
4
4
  const LABEL ={
5
5
  auto: '✅ automático',
@@ -62,7 +62,7 @@ export function scanReport({ inventory, findings, packs, changed, upgrades = nul
62
62
  const KIND = { sdk: 'SDK', http: 'HTTP directo', 'ai-model': 'Modelos de IA' };
63
63
  out.push('| Tipo | API | Rubro | Confianza | Operaciones (usos) | Pack |', '|---|---|---|---|---|---|');
64
64
  for (const api of inventory.apis) {
65
- const ops = api.operations.map((o) => `\`${o.op}\` ×${o.count}`).join(', ') || 'declarada, sin usos encontrados';
65
+ const ops = api.operations.map((o) => `\`${o.op}\` ×${o.count}`).join(', ') || noUsesLabel(api);
66
66
  out.push(`| ${KIND[api.kind]} | ${apiLabel(api)} | ${api.category || '—'} | ${api.confidence} | ${ops} | ${api.packs.length ? 'sí' : '—'} |`);
67
67
  }
68
68
  out.push('');
@@ -25,8 +25,16 @@ jobs:
25
25
  - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
26
26
  with:
27
27
  node-version: 24
28
- - name: Instalar dependencias del proyecto (para correr sus tests antes de cada PR)
28
+ # Las dependencias del proyecto, para correr sus tests antes de cada PR. Cada paso corre solo si el repo tiene
29
+ # ese archivo: en un repo PHP no hay package-lock.json y `npm ci` fallaría.
30
+ - name: Instalar dependencias de Node
31
+ if: hashFiles('package-lock.json') != ''
29
32
  run: npm ci
33
+ # Composer viene instalado en los runners de Ubuntu de GitHub. Si tu proyecto pide otra versión de PHP,
34
+ # agregá antes un paso que la instale.
35
+ - name: Instalar dependencias de PHP
36
+ if: hashFiles('composer.json') != ''
37
+ run: composer install --no-interaction --no-progress
30
38
  # v0 se mueve con cada release 0.x. Para fijar una versión, cambiá v0 por el commit de ese release
31
39
  # (https://github.com/Pepo08/parche/releases), por ejemplo: Pepo08/parche@<sha> # v0.6.7
32
40
  - uses: Pepo08/parche@v0