parche-cli 0.7.2 → 0.7.3
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 +12 -0
- package/README.md +5 -1
- package/bin/parche.js +5 -3
- package/package.json +1 -1
- package/src/engine.js +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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, de la 0.6.0 a la 0.7.0 y de la 0.7.2 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 0.7.1 tampoco: salió junto con la 0.7.2. 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.7.3 — 2026-10-24
|
|
6
|
+
|
|
7
|
+
La foto de cada corrida de la Action. Hasta la 0.7.2, el inventario de APIs, los avisos y el registro se escribían dentro del runner y se perdían al terminar el job: de una corrida quedaban solo el resumen del job y los PRs, y el resumen no se puede leer por la API de GitHub.
|
|
8
|
+
|
|
9
|
+
### Nuevo
|
|
10
|
+
- **La Action guarda la foto de la corrida** como artifact del workflow (`parche`): `scan.json`, que es la salida de `parche scan --json` (el inventario y los avisos), y `ledger.jsonl`, el registro de esa corrida (los PRs que abrió y lo que quedó sin PR). Lleva rutas, números de línea, paquetes, versiones, operaciones y los mensajes de Parche; no lleva código. Se baja con `gh run download <id> -n parche`. Inputs nuevos: `snapshot` (`'true'` por defecto; `'false'` la apaga) y `snapshot-name`, para usar la Action más de una vez en el mismo workflow. Corre aunque `parche pr` haya fallado, y si la foto falla, la corrida no falla: queda un aviso.
|
|
11
|
+
- **Cada aviso tiene un `fingerprint`**, un identificador que sale del pack, el archivo y el texto de la línea, sin el número de línea ni el mensaje. No cambia si se mueven las líneas, se reindenta el código o el archivo pasa a CRLF; sí cambia si cambia el código de esa línea. Sirve para comparar una corrida con la anterior: qué es nuevo, qué sigue y qué se resolvió.
|
|
12
|
+
- **`parche scan --json` dice qué versión de Parche lo generó** (`parcheVersion`).
|
|
13
|
+
|
|
14
|
+
### Cambios
|
|
15
|
+
- `actions/upload-artifact` (v7.0.2) fijada por SHA, como las demás Actions; un test controla que `action.yml` también las fije.
|
|
16
|
+
|
|
5
17
|
## 0.7.2 — 2026-10-24
|
|
6
18
|
|
|
7
19
|
ARCA: lo que pidió el re-escaneo de repos públicos con la 0.7.0. Un PR preparado quedaba parcial sin decirlo, y dos casos podían sacar un PR que no anda.
|
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Copy [`templates/github-workflow.yml`](templates/github-workflow.yml) to `.githu
|
|
|
25
25
|
| `verify` | `'true'` | Run the repo's tests (`npm test`, `composer test` or `vendor/bin/phpunit`) before opening each PR. |
|
|
26
26
|
| `dry-run` | `'false'` | Build the branches and PR bodies without opening any PR. |
|
|
27
27
|
| `token` | `github.token` | Pushes the branches and opens the PRs. With the default `GITHUB_TOKEN`, GitHub holds the PR's workflows until someone approves them; with a GitHub App token or a personal token they run on their own. |
|
|
28
|
+
| `snapshot` | `'true'` | Save a snapshot of the run (API inventory, findings and the PRs it opened, as JSON; no source code) as a workflow artifact. |
|
|
29
|
+
| `snapshot-name` | `parche` | Artifact name. Use a different one for each step if you call the Action more than once in a workflow. |
|
|
28
30
|
|
|
29
31
|
`@v0` follows every 0.x release; to pin one, use its release commit: `Pepo08/parche@<sha> # v0.7.2`. PR bodies and the job output are in Spanish for now.
|
|
30
32
|
|
|
@@ -115,6 +117,8 @@ El workflow necesita `permissions: contents: write` y `pull-requests: write`. An
|
|
|
115
117
|
|
|
116
118
|
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).
|
|
117
119
|
|
|
120
|
+
**La foto de la corrida:** además, la Action guarda un artifact del workflow (`parche`, salvo que le pongas otro nombre con `snapshot-name`) con dos archivos. `scan.json` es la salida de `parche scan --json`: el inventario de APIs y los avisos, cada uno con su `fingerprint`, un identificador que no cambia aunque se muevan las líneas, para comparar una semana con la anterior. `ledger.jsonl` es el registro de esa corrida: los PRs que abrió y lo que quedó sin PR. Lleva rutas, números de línea, paquetes, versiones, operaciones y los mensajes de Parche; no lleva código. Lo baja quien puede ver las corridas de Actions del repo (`gh run download <id> -n parche`), y dura lo que diga la retención de artifacts del repo. Si no lo querés: `snapshot: 'false'`. Si la foto falla, la corrida no falla: queda un aviso.
|
|
121
|
+
|
|
118
122
|
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`:
|
|
119
123
|
|
|
120
124
|
```yaml
|
|
@@ -171,7 +175,7 @@ Parche corre en tu máquina o en tu runner. Tu código no sale de ahí, salvo el
|
|
|
171
175
|
| **Red** | `scan`, `fix` y `packs` no usan la red. `pr` llama a `api.github.com` para abrir el PR en tu repo y pushea su rama `parche/<pack>` (con `--force`, así solo reemplaza su propio PR anterior). `diff` y `scan --upgrades` bajan metadatos y paquetes de `registry.npmjs.org`, que ve qué paquetes y versiones consultás. Nada más. |
|
|
172
176
|
| **Telemetría** | Ninguna. `parche feedback` escribe en `.parche/ledger.jsonl` de tu repo y queda ahí. |
|
|
173
177
|
| **Qué ejecuta** | `git` y, solo con `--verify`, los tests de tu repo (`npm test`, `composer test` o `vendor/bin/phpunit`). |
|
|
174
|
-
| **Qué escribe** | Tu working copy (solo `fix` y `pr`), `.parche/` (se agrega a `.git/info/exclude`) y, para `diff` y `--upgrades`, un caché en `~/.parche/cache`. |
|
|
178
|
+
| **Qué escribe** | Tu working copy (solo `fix` y `pr`), `.parche/` (se agrega a `.git/info/exclude`) y, para `diff` y `--upgrades`, un caché en `~/.parche/cache`. En GitHub Actions, además, la foto de la corrida como artifact de tu workflow (inventario y avisos en JSON, sin código; se apaga con `snapshot: 'false'`). |
|
|
175
179
|
|
|
176
180
|
Para reportar una vulnerabilidad, y el detalle de todo esto: [`SECURITY.md`](SECURITY.md). Si te llegó un PR de Parche: [por qué lo recibiste](docs/por-que-recibi-este-pr.md).
|
|
177
181
|
|
package/bin/parche.js
CHANGED
|
@@ -139,12 +139,13 @@ function printFindings(result) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
const VERSION = () => JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
143
|
+
|
|
142
144
|
async function main() {
|
|
143
145
|
const args = parseArgs(process.argv.slice(2));
|
|
144
146
|
const [cmd, dirArg] = args._;
|
|
145
147
|
if (args.version || cmd === '-v') {
|
|
146
|
-
|
|
147
|
-
console.log(pkg.version);
|
|
148
|
+
console.log(VERSION());
|
|
148
149
|
return 0;
|
|
149
150
|
}
|
|
150
151
|
if (!cmd || args.help) { console.log(HELP); return 0; }
|
|
@@ -187,7 +188,8 @@ async function main() {
|
|
|
187
188
|
fs.writeFileSync(path.join(root, '.parche', 'inventory.json'), JSON.stringify(result.inventory, null, 2));
|
|
188
189
|
fs.writeFileSync(path.join(root, '.parche', 'report.md'), scanReport({ ...result, changed: [], upgrades }));
|
|
189
190
|
record(root, { type: 'scan', findings: counts(result.findings) });
|
|
190
|
-
|
|
191
|
+
// parcheVersion: qué versión (y por lo tanto qué reglas) generó este JSON; la foto de cada corrida de la Action lo guarda.
|
|
192
|
+
if (args.json) { console.log(JSON.stringify({ parcheVersion: VERSION(), inventory: result.inventory, findings: result.findings, skipped: result.skipped }, null, 2)); return 0; }
|
|
191
193
|
console.log(`Parche · scan de ${path.basename(root)}\n`);
|
|
192
194
|
printInventory(result.inventory);
|
|
193
195
|
if (upgrades) printUpgrades(upgrades);
|
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Motor: recorre el repo, arma el inventario de APIs y corre los packs.
|
|
2
|
+
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { loadPacks, transformFor, languageOf } from './packs.js';
|
|
@@ -33,6 +34,18 @@ export function listFiles(dir, skipped = []) {
|
|
|
33
34
|
return out.sort((a, b) => (toPosix(a) < toPosix(b) ? -1 : 1));
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// Identificador estable de un aviso (P5 de docs/app-vigilancia.md): el pack, el archivo y el texto de la línea, sin el
|
|
38
|
+
// número de línea ni el mensaje. Así una corrida reconoce el mismo aviso de la anterior aunque se muevan las líneas o
|
|
39
|
+
// cambie el texto del pack, y "nuevo", "sigue" y "resuelto" no dependen de la posición. Los espacios y el \r no cuentan
|
|
40
|
+
// (una reindentación o pasar a CRLF no es otro aviso). Si el mismo texto se repite en el archivo, cuenta el orden.
|
|
41
|
+
export function fingerprint(finding, source, seen) {
|
|
42
|
+
const text = finding.line ? (source.split('\n')[finding.line - 1] ?? '').replace(/\s+/g, ' ').trim() : '';
|
|
43
|
+
const key = [finding.pack, finding.file, text].join('\u0000');
|
|
44
|
+
const n = (seen.get(key) || 0) + 1;
|
|
45
|
+
seen.set(key, n);
|
|
46
|
+
return crypto.createHash('sha256').update(`${key}\u0000${n}`).digest('hex').slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
export function run(dir, { write = false, packIds = null, today } = {}) {
|
|
37
50
|
const root = path.resolve(dir);
|
|
38
51
|
const packs = loadPacks().filter((p) => !packIds || packIds.includes(p.id));
|
|
@@ -47,6 +60,7 @@ export function run(dir, { write = false, packIds = null, today } = {}) {
|
|
|
47
60
|
const usages = [];
|
|
48
61
|
const fileScans = []; // inventario de cualquier API (nivel 1), por archivo
|
|
49
62
|
const changed = new Map(); // archivo relativo → [packs]
|
|
63
|
+
const seen = new Map(); // para fingerprint(): cuántas veces apareció cada pack + archivo + texto
|
|
50
64
|
|
|
51
65
|
files.forEach((abs, i) => {
|
|
52
66
|
const rel = rels[i];
|
|
@@ -60,10 +74,12 @@ export function run(dir, { write = false, packIds = null, today } = {}) {
|
|
|
60
74
|
try {
|
|
61
75
|
res = t({ source, file: rel, pack, today, context });
|
|
62
76
|
} catch (err) {
|
|
63
|
-
|
|
77
|
+
const f = { pack: pack.id, file: rel, line: null, status: 'needs-input', message: `Parche no pudo procesar este archivo: ${err.message}` };
|
|
78
|
+
findings.push({ ...f, fingerprint: fingerprint(f, source, seen) });
|
|
64
79
|
continue;
|
|
65
80
|
}
|
|
66
|
-
|
|
81
|
+
// El texto de la línea, del archivo tal como lo leyó este pack (antes de su propio cambio).
|
|
82
|
+
findings.push(...res.findings.map((f) => ({ ...f, fingerprint: fingerprint(f, source, seen) })));
|
|
67
83
|
usages.push(...res.usages.map((u) => ({ ...u, file: rel, pack: pack.id })));
|
|
68
84
|
if (res.output !== null && res.output !== source) {
|
|
69
85
|
source = res.output;
|