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 +13 -0
- package/LICENSE +1 -1
- package/README.md +3 -1
- package/bin/parche.js +21 -2
- package/package.json +2 -2
- package/src/actions.js +72 -0
- package/src/inventory/index.js +8 -0
- package/src/report.js +2 -2
- package/templates/github-workflow.yml +9 -1
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
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(', ') ||
|
|
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.
|
|
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": "
|
|
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
|
+
}
|
package/src/inventory/index.js
CHANGED
|
@@ -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(', ') ||
|
|
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
|
-
|
|
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
|