@wallavi/vercel-build 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1011 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var crypto = require('crypto');
5
+ var fs = require('fs');
6
+ var path3 = require('path');
7
+ var zod = require('zod');
8
+ var child_process = require('child_process');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
29
+
30
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
31
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
32
+ }) : x)(function(x) {
33
+ if (typeof require !== "undefined") return require.apply(this, arguments);
34
+ throw Error('Dynamic require of "' + x + '" is not supported');
35
+ });
36
+ var API_VERCEL_URL = "https://api.vercel.com";
37
+ var LIMITE_PAGINA_VERCEL = 100;
38
+ var MAX_PAGINAS_VERCEL = 1e3;
39
+ var ESTADOS_DEPLOYMENT = [
40
+ "BUILDING",
41
+ "ERROR",
42
+ "INITIALIZING",
43
+ "QUEUED",
44
+ "READY",
45
+ "CANCELED"
46
+ ];
47
+ var ErrorContratoVercel = class extends Error {
48
+ constructor(mensaje) {
49
+ super(mensaje);
50
+ this.name = "ErrorContratoVercel";
51
+ }
52
+ };
53
+ var ErrorRedVercel = class extends Error {
54
+ estado;
55
+ constructor(mensaje, estado) {
56
+ super(mensaje);
57
+ this.name = "ErrorRedVercel";
58
+ if (estado !== void 0) this.estado = estado;
59
+ }
60
+ };
61
+ var esquemaPaginacion = zod.z.object({
62
+ count: zod.z.number().int().nonnegative(),
63
+ next: zod.z.number().finite().nullable(),
64
+ prev: zod.z.number().finite().nullable()
65
+ }).passthrough();
66
+ var esquemaEquipo = zod.z.object({
67
+ id: zod.z.string().regex(/^team_.+/),
68
+ slug: zod.z.string().min(1)
69
+ }).passthrough();
70
+ var esquemaPaginaTeams = zod.z.object({
71
+ teams: zod.z.array(esquemaEquipo),
72
+ pagination: esquemaPaginacion
73
+ }).passthrough();
74
+ var esquemaEstadoDeployment = zod.z.enum(ESTADOS_DEPLOYMENT);
75
+ var esquemaDeployment = zod.z.object({
76
+ uid: zod.z.string().min(1),
77
+ name: zod.z.string().min(1),
78
+ createdAt: zod.z.number().finite(),
79
+ state: esquemaEstadoDeployment.optional(),
80
+ readyState: esquemaEstadoDeployment.optional(),
81
+ buildingAt: zod.z.number().finite().optional(),
82
+ ready: zod.z.number().finite().optional()
83
+ }).passthrough().refine((d) => d.state !== void 0 || d.readyState !== void 0, {
84
+ message: "cada deployment debe incluir state o readyState"
85
+ });
86
+ var esquemaPaginaDeployments = zod.z.object({
87
+ deployments: zod.z.array(esquemaDeployment),
88
+ pagination: esquemaPaginacion
89
+ }).passthrough();
90
+ var esquemaArchivoDeployments = zod.z.object({
91
+ paginas: zod.z.array(esquemaPaginaDeployments).min(1)
92
+ }).passthrough();
93
+ var SCRIPT_FETCH = [
94
+ "const [u] = process.argv.slice(1), h = require('node:fs').readFileSync(0, 'utf8');",
95
+ "fetch(u, { method: 'GET', headers: JSON.parse(h) })",
96
+ " .then(async (r) => process.stdout.write(JSON.stringify({ estado: r.status, cuerpo: await r.text() })))",
97
+ " .catch((e) => { process.stderr.write(String(e?.message || e)); process.exit(1); });"
98
+ ].join("\n");
99
+ var solicitarVercel = (url, headers) => {
100
+ const r = child_process.spawnSync(process.execPath, ["-e", SCRIPT_FETCH, url], {
101
+ input: JSON.stringify(headers),
102
+ shell: false,
103
+ encoding: "utf8",
104
+ maxBuffer: 64 * 1024 * 1024
105
+ });
106
+ if (r.error || r.status !== 0) {
107
+ throw new ErrorRedVercel(
108
+ r.error?.message ?? (r.stderr.trim() || `fallo de red en ${url}`)
109
+ );
110
+ }
111
+ return JSON.parse(r.stdout);
112
+ };
113
+ function pedirJsonVercel(rutaQuery, token, solicitar = solicitarVercel, baseUrl = API_VERCEL_URL) {
114
+ let respuesta;
115
+ try {
116
+ respuesta = solicitar(`${baseUrl}${rutaQuery}`, {
117
+ Authorization: `Bearer ${token}`
118
+ });
119
+ } catch (e) {
120
+ if (e instanceof ErrorRedVercel || e instanceof ErrorContratoVercel)
121
+ throw e;
122
+ throw new ErrorRedVercel(e.message);
123
+ }
124
+ if (respuesta.estado < 200 || respuesta.estado >= 300) {
125
+ throw new ErrorRedVercel(
126
+ `HTTP ${respuesta.estado} en GET ${rutaQuery}`,
127
+ respuesta.estado
128
+ );
129
+ }
130
+ try {
131
+ return { estado: respuesta.estado, valor: JSON.parse(respuesta.cuerpo) };
132
+ } catch (e) {
133
+ throw new ErrorContratoVercel(
134
+ `JSON inv\xE1lido en GET ${rutaQuery}: ${e.message}`
135
+ );
136
+ }
137
+ }
138
+ function resolverEquipo(equipo, token, solicitar = solicitarVercel, baseUrl = API_VERCEL_URL) {
139
+ if (/^team_.+/.test(equipo)) return equipo;
140
+ let until;
141
+ for (let pagina = 1; ; pagina++) {
142
+ const query = until === void 0 ? `limit=${LIMITE_PAGINA_VERCEL}` : `limit=${LIMITE_PAGINA_VERCEL}&until=${until}`;
143
+ const { valor } = pedirJsonVercel(
144
+ `/v2/teams?${query}`,
145
+ token,
146
+ solicitar,
147
+ baseUrl
148
+ );
149
+ const r = esquemaPaginaTeams.safeParse(valor);
150
+ if (!r.success) {
151
+ throw new ErrorContratoVercel(
152
+ `GET /v2/teams inv\xE1lido: ${r.error.message}`
153
+ );
154
+ }
155
+ const encontrado = r.data.teams.find((t) => t.slug === equipo);
156
+ if (encontrado) return encontrado.id;
157
+ const next = r.data.pagination.next;
158
+ if (next === null) {
159
+ throw new ErrorContratoVercel(`no se encontr\xF3 el equipo: ${equipo}`);
160
+ }
161
+ if (until !== void 0 && next >= until) {
162
+ throw new ErrorContratoVercel(`pagination.next (${next}) no decrece`);
163
+ }
164
+ if (pagina >= MAX_PAGINAS_VERCEL) {
165
+ throw new ErrorContratoVercel(`super\xF3 ${MAX_PAGINAS_VERCEL} p\xE1ginas`);
166
+ }
167
+ until = next;
168
+ }
169
+ }
170
+ function leerRespuestaDeployments(ruta, untilInicial) {
171
+ if (!fs.existsSync(ruta)) {
172
+ throw new ErrorContratoVercel(`no existe el archivo: ${ruta}`);
173
+ }
174
+ let crudo;
175
+ try {
176
+ crudo = JSON.parse(fs.readFileSync(ruta, "utf8"));
177
+ } catch (e) {
178
+ throw new ErrorContratoVercel(
179
+ `${ruta}: JSON inv\xE1lido: ${e.message}`
180
+ );
181
+ }
182
+ const rArchivo = esquemaArchivoDeployments.safeParse(crudo);
183
+ const rPagina = rArchivo.success ? null : esquemaPaginaDeployments.safeParse(crudo);
184
+ const paginas = rArchivo.success ? rArchivo.data.paginas : rPagina?.success ? [rPagina.data] : null;
185
+ if (!paginas || paginas.length > MAX_PAGINAS_VERCEL) {
186
+ throw new ErrorContratoVercel(
187
+ `${ruta}: estructura de deployments inv\xE1lida`
188
+ );
189
+ }
190
+ let cursorPrevio = untilInicial;
191
+ paginas.forEach((p, i) => {
192
+ const next = p.pagination.next;
193
+ const esUltima = i === paginas.length - 1;
194
+ if (!esUltima && next === null || esUltima && next !== null) {
195
+ throw new ErrorContratoVercel(
196
+ `paginaci\xF3n inconsistente en p\xE1gina ${i + 1}`
197
+ );
198
+ }
199
+ if (next !== null && cursorPrevio !== void 0 && next >= cursorPrevio) {
200
+ throw new ErrorContratoVercel(`pagination.next (${next}) no decrece`);
201
+ }
202
+ if (next !== null) cursorPrevio = next;
203
+ });
204
+ return paginas;
205
+ }
206
+ function calcularMinutosPorDia(paginas, desde, hasta) {
207
+ const dias = /* @__PURE__ */ new Map();
208
+ for (let t = Date.parse(`${desde}T00:00:00.000Z`); t <= Date.parse(`${hasta}T00:00:00.000Z`); t += 864e5) {
209
+ dias.set(new Date(t).toISOString().slice(0, 10), /* @__PURE__ */ new Map());
210
+ }
211
+ for (const pagina of paginas) {
212
+ for (const d of pagina.deployments) {
213
+ if (d.state !== "READY" && d.readyState !== "READY" || d.buildingAt === void 0 || d.ready === void 0)
214
+ continue;
215
+ const porProyecto = dias.get(
216
+ new Date(d.buildingAt).toISOString().slice(0, 10)
217
+ );
218
+ porProyecto?.set(
219
+ d.name,
220
+ (porProyecto.get(d.name) ?? 0) + (d.ready - d.buildingAt)
221
+ );
222
+ }
223
+ }
224
+ const r2 = (ms) => Math.round(ms / 6e4 * 100) / 100;
225
+ return {
226
+ dias: [...dias.entries()].map(([fecha, porProyecto]) => ({
227
+ fecha,
228
+ minutos: r2([...porProyecto.values()].reduce((a, b) => a + b, 0)),
229
+ porProyecto: Object.fromEntries(
230
+ [...porProyecto.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([nombre, ms]) => [nombre, r2(ms)])
231
+ )
232
+ }))
233
+ };
234
+ }
235
+ function leerArgumentosMedir(args) {
236
+ const f = leerFlagsCli(args, [
237
+ "--equipo",
238
+ "--desde",
239
+ "--hasta",
240
+ "--respuesta"
241
+ ]);
242
+ if (!f.ok) return f;
243
+ const { equipo, desde, hasta, respuesta } = f.valores;
244
+ if (!equipo) return { ok: false, motivo: "falta --equipo <slug|team_id>" };
245
+ if (!desde) return { ok: false, motivo: "falta --desde <AAAA-MM-DD>" };
246
+ if (!hasta) return { ok: false, motivo: "falta --hasta <AAAA-MM-DD>" };
247
+ const rango = rangoFechasUtc(desde, hasta);
248
+ if (!rango.ok) return rango;
249
+ return {
250
+ ok: true,
251
+ valor: { equipo, desde, hasta, ...respuesta ? { respuesta } : {} }
252
+ };
253
+ }
254
+ function ejecutarMedir(opciones) {
255
+ const rango = rangoFechasUtc(opciones.desde, opciones.hasta);
256
+ if (!rango.ok) {
257
+ opciones.error(`wallavi-vercel medir: ${rango.motivo}`);
258
+ return 2;
259
+ }
260
+ const token = opciones.env.VERCEL_TOKEN?.trim() ?? "";
261
+ try {
262
+ let paginas;
263
+ if (opciones.respuesta !== void 0) {
264
+ paginas = leerRespuestaDeployments(
265
+ path3__namespace.resolve(opciones.cwd, opciones.respuesta),
266
+ rango.until
267
+ );
268
+ } else {
269
+ if (!token) {
270
+ opciones.error("wallavi-vercel medir: falta VERCEL_TOKEN");
271
+ return 2;
272
+ }
273
+ const baseUrl = opciones.baseUrl ?? opciones.env.VERCEL_API_URL?.trim() ?? API_VERCEL_URL;
274
+ const teamId = resolverEquipo(
275
+ opciones.equipo,
276
+ token,
277
+ opciones.solicitar,
278
+ baseUrl
279
+ );
280
+ paginas = consultarDeployments({
281
+ teamId,
282
+ since: rango.since,
283
+ until: rango.until,
284
+ token,
285
+ solicitar: opciones.solicitar,
286
+ baseUrl
287
+ }).paginas;
288
+ }
289
+ opciones.salida(
290
+ JSON.stringify(
291
+ calcularMinutosPorDia(paginas, opciones.desde, opciones.hasta),
292
+ null,
293
+ 2
294
+ )
295
+ );
296
+ return 0;
297
+ } catch (e) {
298
+ opciones.error(
299
+ `wallavi-vercel medir: ${redactarToken(e.message, token)}`
300
+ );
301
+ return e instanceof ErrorContratoVercel ? 2 : 1;
302
+ }
303
+ }
304
+ function comandoMedir(args, entorno) {
305
+ const leido = leerArgumentosMedir(args);
306
+ if (!leido.ok) {
307
+ entorno.error(`wallavi-vercel medir: ${leido.motivo}`);
308
+ return 2;
309
+ }
310
+ return ejecutarMedir({ ...entorno, ...leido.valor });
311
+ }
312
+ var medir = comandoMedir;
313
+
314
+ // src/comandos/capturar.ts
315
+ var ARCHIVO_DEPLOYMENTS_V6 = "deployments-v6.json";
316
+ var ARCHIVO_CAPTURA_V6 = "captura-v6.json";
317
+ var REFERENCIA_DEPLOYMENTS_V6 = "https://vercel.com/docs/rest-api/reference/endpoints/deployments/list-deployments";
318
+ function redactarToken(texto, token) {
319
+ const limpio = token?.trim();
320
+ return limpio ? texto.split(limpio).join("[REDACTED]") : texto;
321
+ }
322
+ function validarFechaIso(fecha) {
323
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) return null;
324
+ const ms = Date.parse(`${fecha}T00:00:00.000Z`);
325
+ return !Number.isNaN(ms) && new Date(ms).toISOString().slice(0, 10) === fecha ? ms : null;
326
+ }
327
+ function rangoFechasUtc(desde, hasta) {
328
+ const desdeMs = validarFechaIso(desde);
329
+ if (desdeMs === null)
330
+ return { ok: false, motivo: `--desde inv\xE1lida: ${desde}` };
331
+ const hastaMs = validarFechaIso(hasta);
332
+ if (hastaMs === null)
333
+ return { ok: false, motivo: `--hasta inv\xE1lida: ${hasta}` };
334
+ if (desdeMs > hastaMs) {
335
+ return {
336
+ ok: false,
337
+ motivo: `--desde (${desde}) posterior a --hasta (${hasta})`
338
+ };
339
+ }
340
+ return {
341
+ ok: true,
342
+ since: desdeMs,
343
+ until: Date.parse(`${hasta}T23:59:59.999Z`)
344
+ };
345
+ }
346
+ function leerFlagsCli(args, permitidas) {
347
+ const valores = {};
348
+ for (let i = 0; i < args.length; i++) {
349
+ const arg = args[i] ?? "";
350
+ const flag = permitidas.find((p) => arg === p || arg.startsWith(`${p}=`));
351
+ if (!flag) return { ok: false, motivo: `argumento no reconocido: ${arg}` };
352
+ const v = arg === flag ? args[++i] : arg.slice(flag.length + 1);
353
+ if (!v || v.trim() === "" || v.startsWith("-")) {
354
+ return { ok: false, motivo: `falta el valor de ${flag}` };
355
+ }
356
+ valores[flag.slice(2)] = v.trim();
357
+ }
358
+ return { ok: true, valores };
359
+ }
360
+ function consultarDeployments(opciones) {
361
+ const solicitar = opciones.solicitar ?? solicitarVercel;
362
+ const baseUrl = opciones.baseUrl ?? API_VERCEL_URL;
363
+ const paginas = [];
364
+ const pedidos = [];
365
+ let untilActual = opciones.until;
366
+ for (let pagina = 1; ; pagina++) {
367
+ const since = String(opciones.since);
368
+ const until = String(untilActual);
369
+ const limit = String(LIMITE_PAGINA_VERCEL);
370
+ const query = `teamId=${encodeURIComponent(opciones.teamId)}&since=${since}&until=${until}&limit=${limit}`;
371
+ const { estado, valor } = pedirJsonVercel(
372
+ `/v6/deployments?${query}`,
373
+ opciones.token,
374
+ solicitar,
375
+ baseUrl
376
+ );
377
+ const r = esquemaPaginaDeployments.safeParse(valor);
378
+ if (!r.success) {
379
+ throw new ErrorContratoVercel(
380
+ `GET /v6/deployments inv\xE1lido: ${r.error.message}`
381
+ );
382
+ }
383
+ paginas.push(r.data);
384
+ pedidos.push({ query: { since, until, limit }, estado });
385
+ const next = r.data.pagination.next;
386
+ if (next === null) return { paginas, pedidos };
387
+ if (next >= untilActual) {
388
+ throw new ErrorContratoVercel(`pagination.next (${next}) no decrece`);
389
+ }
390
+ if (pagina >= MAX_PAGINAS_VERCEL) {
391
+ throw new ErrorContratoVercel(`super\xF3 ${MAX_PAGINAS_VERCEL} p\xE1ginas`);
392
+ }
393
+ untilActual = next;
394
+ }
395
+ }
396
+ var esquemaPedidoCaptura = zod.z.object({
397
+ query: zod.z.object({
398
+ since: zod.z.string().min(1),
399
+ until: zod.z.string().min(1),
400
+ limit: zod.z.string().min(1)
401
+ }).strict(),
402
+ estado: zod.z.number().int()
403
+ }).passthrough();
404
+ var esquemaCapturaV6 = zod.z.object({
405
+ origen: zod.z.enum(["documentacion", "captura"]),
406
+ referencia: zod.z.string().min(1),
407
+ fecha: zod.z.string().min(1),
408
+ metodo: zod.z.literal("GET"),
409
+ ruta: zod.z.literal("/v6/deployments"),
410
+ desde: zod.z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
411
+ hasta: zod.z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
412
+ pedidos: zod.z.array(esquemaPedidoCaptura),
413
+ archivo: zod.z.string().min(1),
414
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/),
415
+ transformaciones: zod.z.array(zod.z.string())
416
+ }).passthrough();
417
+ function leerArgumentosCapturar(args) {
418
+ const f = leerFlagsCli(args, ["--equipo", "--desde", "--hasta", "--salida"]);
419
+ if (!f.ok) return f;
420
+ const { equipo, desde, hasta, salida } = f.valores;
421
+ if (!equipo) return { ok: false, motivo: "falta --equipo <slug|team_id>" };
422
+ if (!desde) return { ok: false, motivo: "falta --desde <AAAA-MM-DD>" };
423
+ if (!hasta) return { ok: false, motivo: "falta --hasta <AAAA-MM-DD>" };
424
+ if (!salida) return { ok: false, motivo: "falta --salida <carpeta>" };
425
+ const rango = rangoFechasUtc(desde, hasta);
426
+ if (!rango.ok) return rango;
427
+ return { ok: true, valor: { equipo, desde, hasta, salida } };
428
+ }
429
+ function armarCapturaV6(opciones) {
430
+ const sha256 = crypto.createHash("sha256").update(opciones.contenidoDeployments, "utf8").digest("hex");
431
+ return {
432
+ origen: opciones.origen ?? "captura",
433
+ referencia: opciones.referencia ?? REFERENCIA_DEPLOYMENTS_V6,
434
+ fecha: opciones.fecha ?? (/* @__PURE__ */ new Date()).toISOString(),
435
+ metodo: "GET",
436
+ ruta: "/v6/deployments",
437
+ desde: opciones.desde,
438
+ hasta: opciones.hasta,
439
+ pedidos: opciones.pedidos.map((p) => ({
440
+ query: { ...p.query },
441
+ estado: p.estado
442
+ })),
443
+ archivo: ARCHIVO_DEPLOYMENTS_V6,
444
+ sha256,
445
+ transformaciones: opciones.transformaciones ?? []
446
+ };
447
+ }
448
+ function ejecutarCapturar(opciones) {
449
+ const rango = rangoFechasUtc(opciones.desde, opciones.hasta);
450
+ if (!rango.ok) {
451
+ opciones.error(`wallavi-vercel capturar: ${rango.motivo}`);
452
+ return 2;
453
+ }
454
+ const token = opciones.env.VERCEL_TOKEN?.trim() ?? "";
455
+ if (!token) {
456
+ opciones.error("wallavi-vercel capturar: falta VERCEL_TOKEN");
457
+ return 2;
458
+ }
459
+ try {
460
+ const baseUrl = opciones.baseUrl ?? opciones.env.VERCEL_API_URL?.trim() ?? API_VERCEL_URL;
461
+ const teamId = resolverEquipo(
462
+ opciones.equipo,
463
+ token,
464
+ opciones.solicitar,
465
+ baseUrl
466
+ );
467
+ const { paginas, pedidos } = consultarDeployments({
468
+ teamId,
469
+ since: rango.since,
470
+ until: rango.until,
471
+ token,
472
+ solicitar: opciones.solicitar,
473
+ baseUrl
474
+ });
475
+ const rutaCarpeta = path3__namespace.resolve(opciones.cwd, opciones.salida);
476
+ fs.mkdirSync(rutaCarpeta, { recursive: true });
477
+ const contenidoDeployments = `${JSON.stringify({ paginas }, null, 2)}
478
+ `;
479
+ fs.writeFileSync(
480
+ path3__namespace.join(rutaCarpeta, ARCHIVO_DEPLOYMENTS_V6),
481
+ contenidoDeployments,
482
+ "utf8"
483
+ );
484
+ const captura = armarCapturaV6({
485
+ desde: opciones.desde,
486
+ hasta: opciones.hasta,
487
+ pedidos,
488
+ contenidoDeployments,
489
+ origen: opciones.origen,
490
+ referencia: opciones.referencia,
491
+ fecha: opciones.fecha,
492
+ transformaciones: opciones.transformaciones
493
+ });
494
+ fs.writeFileSync(
495
+ path3__namespace.join(rutaCarpeta, ARCHIVO_CAPTURA_V6),
496
+ `${JSON.stringify(captura, null, 2)}
497
+ `,
498
+ "utf8"
499
+ );
500
+ opciones.salidaTexto?.(JSON.stringify(captura, null, 2));
501
+ return 0;
502
+ } catch (e) {
503
+ opciones.error(
504
+ `wallavi-vercel capturar: ${redactarToken(e.message, token)}`
505
+ );
506
+ return e instanceof ErrorContratoVercel ? 2 : 1;
507
+ }
508
+ }
509
+ function comandoCapturar(args, entorno) {
510
+ const leido = leerArgumentosCapturar(args);
511
+ if (!leido.ok) {
512
+ entorno.error(`wallavi-vercel capturar: ${leido.motivo}`);
513
+ return 2;
514
+ }
515
+ return ejecutarCapturar({
516
+ cwd: entorno.cwd,
517
+ env: entorno.env,
518
+ error: entorno.error,
519
+ salidaTexto: entorno.salida,
520
+ ...leido.valor
521
+ });
522
+ }
523
+ var capturar = comandoCapturar;
524
+ var ARCHIVO_CONFIG = "vercel-build.config.json";
525
+ function normalizarCarpeta(carpeta) {
526
+ let limpia = carpeta.trim();
527
+ while (limpia.startsWith("./")) limpia = limpia.slice(2);
528
+ while (limpia.length > 1 && limpia.endsWith("/"))
529
+ limpia = limpia.slice(0, -1);
530
+ return limpia === "" ? "." : limpia;
531
+ }
532
+ var esquemaCarpeta = zod.z.string().min(1).transform(normalizarCarpeta).refine((c) => !c.startsWith("/") && !c.split("/").includes(".."), {
533
+ message: "debe ser una ruta relativa a la ra\xEDz, sin .."
534
+ });
535
+ var esquemaProyecto = zod.z.object({
536
+ carpetas: zod.z.array(esquemaCarpeta),
537
+ carpetasExtra: zod.z.array(esquemaCarpeta).optional()
538
+ }).strict();
539
+ var esquemaConfig = zod.z.object({
540
+ ramasConPreview: zod.z.array(zod.z.string().min(1)),
541
+ proyectos: zod.z.record(zod.z.string().min(1), esquemaProyecto)
542
+ }).strict();
543
+ function configVacia() {
544
+ return { ramasConPreview: [], proyectos: {} };
545
+ }
546
+ function describirProblema(p) {
547
+ const ruta = p.path.join(".");
548
+ const llaves = p.code === "unrecognized_keys" ? p.keys.map((k) => ruta ? `${ruta}.${k}` : k).join(", ") : "";
549
+ return `${llaves || ruta || "(ra\xEDz)"}: ${p.message}`;
550
+ }
551
+ function validarConfig(valor, origen = ARCHIVO_CONFIG) {
552
+ const r = esquemaConfig.safeParse(valor);
553
+ if (!r.success) {
554
+ throw new Error(
555
+ `${origen}: ${r.error.issues.map(describirProblema).join("; ")}`
556
+ );
557
+ }
558
+ return r.data;
559
+ }
560
+ function leerConfig(ruta = ARCHIVO_CONFIG) {
561
+ if (!fs.existsSync(ruta)) return configVacia();
562
+ let valor;
563
+ try {
564
+ valor = JSON.parse(fs.readFileSync(ruta, "utf8"));
565
+ } catch (e) {
566
+ throw new Error(`${ruta}: JSON inv\xE1lido: ${e.message}`);
567
+ }
568
+ return validarConfig(valor, ruta);
569
+ }
570
+ function proyectoDeCarpeta(config, carpeta) {
571
+ const buscada = normalizarCarpeta(carpeta);
572
+ for (const [nombre, proyecto] of Object.entries(config.proyectos)) {
573
+ if (proyecto.carpetas.includes(buscada)) return { nombre, proyecto };
574
+ }
575
+ return null;
576
+ }
577
+
578
+ // src/cambios.ts
579
+ function ejecutar(comando, args, opciones) {
580
+ const r = child_process.spawnSync(comando, args, {
581
+ cwd: opciones.cwd,
582
+ env: opciones.env ?? process.env,
583
+ shell: false,
584
+ encoding: "utf8",
585
+ maxBuffer: 64 * 1024 * 1024,
586
+ stdio: opciones.heredar ? "inherit" : "pipe"
587
+ });
588
+ const fallo = r.error?.message ?? (r.signal ? `termin\xF3 por la se\xF1al ${r.signal}` : void 0);
589
+ return {
590
+ codigo: r.status,
591
+ salida: r.stdout ?? "",
592
+ error: r.stderr ?? "",
593
+ ...fallo ? { fallo } : {}
594
+ };
595
+ }
596
+ var incierto = (motivo) => ({
597
+ resultado: "incierto",
598
+ motivo
599
+ });
600
+ function salidaGit(ej, cwd, args) {
601
+ const r = ej("git", args, { cwd });
602
+ return r.codigo === 0 ? r.salida.trim() : null;
603
+ }
604
+ function ubicarEnRepo(carpeta, ej = ejecutar) {
605
+ const raiz = salidaGit(ej, carpeta, ["rev-parse", "--show-toplevel"]);
606
+ const prefijo = salidaGit(ej, carpeta, ["rev-parse", "--show-prefix"]);
607
+ if (!raiz || prefijo === null) return null;
608
+ return { raiz, prefijo: normalizarCarpeta(prefijo) };
609
+ }
610
+ function nombrePaquete(archivo) {
611
+ if (!fs.existsSync(archivo)) return null;
612
+ try {
613
+ const n = JSON.parse(fs.readFileSync(archivo, "utf8")).name;
614
+ return typeof n === "string" && n !== "" ? n : null;
615
+ } catch {
616
+ return null;
617
+ }
618
+ }
619
+ function comandoTurbo(raiz) {
620
+ const local = path3__namespace.join(raiz, "node_modules", ".bin", "turbo");
621
+ if (fs.existsSync(local)) return [local, []];
622
+ let version = "latest";
623
+ try {
624
+ const pkg = JSON.parse(
625
+ fs.readFileSync(path3__namespace.join(raiz, "package.json"), "utf8")
626
+ );
627
+ const decl = pkg.devDependencies?.turbo;
628
+ if (typeof decl === "string" && decl !== "") version = decl;
629
+ } catch {
630
+ }
631
+ return ["npx", ["--yes", `turbo@${version}`]];
632
+ }
633
+ function paquetesDeTurbo(salida) {
634
+ const i = salida.indexOf("{");
635
+ if (i === -1) return null;
636
+ try {
637
+ const p = JSON.parse(salida.slice(i)).packages;
638
+ return Array.isArray(p) && p.every((x) => typeof x === "string") ? p : null;
639
+ } catch {
640
+ return null;
641
+ }
642
+ }
643
+ function diffGit(ej, cwd, antes, rutas) {
644
+ const r = ej("git", ["diff", "--quiet", antes, "HEAD", "--", ...rutas], {
645
+ cwd
646
+ });
647
+ const lista = rutas.join(", ");
648
+ if (r.codigo === 0)
649
+ return { resultado: "sin-cambios", motivo: `git: sin cambios en ${lista}` };
650
+ if (r.codigo === 1)
651
+ return { resultado: "con-cambios", motivo: `git: hay cambios en ${lista}` };
652
+ return incierto(`git diff fall\xF3 (${r.fallo ?? `c\xF3digo ${r.codigo}`})`);
653
+ }
654
+ function evaluarCambios(opciones) {
655
+ const ej = opciones.ejecutar ?? ejecutar;
656
+ const env = opciones.env ?? process.env;
657
+ const carpeta = path3__namespace.resolve(opciones.carpeta);
658
+ const ubicacion = ubicarEnRepo(carpeta, ej);
659
+ if (!ubicacion) return incierto(`${carpeta} no est\xE1 en un repo de Git`);
660
+ const { raiz, prefijo } = ubicacion;
661
+ const head = salidaGit(ej, raiz, ["rev-parse", "HEAD"]);
662
+ if (!head) return incierto("no se pudo leer HEAD");
663
+ if (env.VERCEL_GIT_COMMIT_SHA && env.VERCEL_GIT_COMMIT_SHA !== head) {
664
+ return incierto(`VERCEL_GIT_COMMIT_SHA no coincide con HEAD (${head})`);
665
+ }
666
+ const antes = opciones.antes ?? env.VERCEL_GIT_PREVIOUS_SHA ?? "";
667
+ if (antes === "") return incierto("no hay commit anterior");
668
+ if (antes.startsWith("-"))
669
+ return incierto(`commit anterior inv\xE1lido: ${antes}`);
670
+ if (ej("git", ["cat-file", "-e", `${antes}^{commit}`], { cwd: raiz }).codigo !== 0) {
671
+ return incierto(`el commit anterior ${antes} no est\xE1 en el clon`);
672
+ }
673
+ let config;
674
+ try {
675
+ config = opciones.config ?? leerConfig(path3__namespace.join(raiz, ARCHIVO_CONFIG));
676
+ } catch (e) {
677
+ return incierto(e.message);
678
+ }
679
+ const proy = proyectoDeCarpeta(config, prefijo)?.proyecto;
680
+ const extras = [
681
+ .../* @__PURE__ */ new Set([...proy?.carpetas ?? [], ...proy?.carpetasExtra ?? []])
682
+ ].filter((c) => c !== prefijo);
683
+ const nombre = nombrePaquete(path3__namespace.join(raiz, prefijo, "package.json"));
684
+ if (!fs.existsSync(path3__namespace.join(raiz, "turbo.json")) || !nombre) {
685
+ return diffGit(ej, raiz, antes, [prefijo, ...extras]);
686
+ }
687
+ const [turbo, base] = comandoTurbo(raiz);
688
+ const r = ej(
689
+ turbo,
690
+ [...base, "build", `--filter=${nombre}...[${antes}]`, "--dry=json"],
691
+ {
692
+ cwd: raiz,
693
+ env
694
+ }
695
+ );
696
+ if (r.codigo !== 0)
697
+ return incierto(`turbo fall\xF3 (${r.fallo ?? `c\xF3digo ${r.codigo}`})`);
698
+ const paquetes = paquetesDeTurbo(r.salida);
699
+ if (!paquetes) return incierto("la salida de turbo no trae packages");
700
+ const afectados = paquetes.filter((p) => p !== "//");
701
+ if (afectados.length > 0) {
702
+ return {
703
+ resultado: "con-cambios",
704
+ motivo: `turbo: afectados ${afectados.join(", ")}`
705
+ };
706
+ }
707
+ if (extras.length > 0) return diffGit(ej, raiz, antes, extras);
708
+ return {
709
+ resultado: "sin-cambios",
710
+ motivo: `turbo: ${nombre} sin paquetes afectados`
711
+ };
712
+ }
713
+
714
+ // src/comandos/desplegar.ts
715
+ var VARIABLES_VERCEL = [
716
+ "VERCEL_TOKEN",
717
+ "VERCEL_ORG_ID",
718
+ "VERCEL_PROJECT_ID"
719
+ ];
720
+ var PASOS_VERCEL = [
721
+ ["pull", "--yes", "--environment=production"],
722
+ ["build", "--prod"],
723
+ ["deploy", "--prebuilt", "--prod"]
724
+ ];
725
+ function leerArgumentosDesplegar(args) {
726
+ let carpeta;
727
+ let antes;
728
+ for (let i = 0; i < args.length; i++) {
729
+ const arg = args[i] ?? "";
730
+ if (arg === "--carpeta" || arg.startsWith("--carpeta=")) {
731
+ const valor = arg === "--carpeta" ? args[++i] : arg.slice("--carpeta=".length);
732
+ if (!valor || valor.trim() === "" || valor.startsWith("-")) {
733
+ return { ok: false, motivo: "falta el valor de --carpeta <ruta>" };
734
+ }
735
+ carpeta = valor.trim();
736
+ continue;
737
+ }
738
+ if (arg === "--antes" || arg.startsWith("--antes=")) {
739
+ const valor = arg === "--antes" ? args[++i] : arg.slice("--antes=".length);
740
+ if (!valor || valor.trim() === "" || valor.startsWith("-")) {
741
+ return { ok: false, motivo: "falta el valor de --antes <sha>" };
742
+ }
743
+ antes = valor.trim();
744
+ continue;
745
+ }
746
+ return { ok: false, motivo: `argumento no reconocido: ${arg}` };
747
+ }
748
+ if (!carpeta) {
749
+ return {
750
+ ok: false,
751
+ motivo: "falta la opci\xF3n obligatoria --carpeta <ruta>"
752
+ };
753
+ }
754
+ return {
755
+ ok: true,
756
+ valor: { carpeta, ...antes !== void 0 ? { antes } : {} }
757
+ };
758
+ }
759
+ function redactarCredenciales(texto, env) {
760
+ let limpio = texto;
761
+ for (const nombre of VARIABLES_VERCEL) {
762
+ const valor = env[nombre]?.trim();
763
+ if (valor) {
764
+ limpio = limpio.split(valor).join("[REDACTED]");
765
+ }
766
+ }
767
+ return limpio;
768
+ }
769
+ function ejecutarDesplegar(opciones) {
770
+ const ej = opciones.ejecutar ?? ejecutar;
771
+ const evaluar = opciones.evaluar ?? evaluarCambios;
772
+ const rutaCarpeta = path3__namespace.resolve(opciones.cwd, opciones.carpeta);
773
+ if (!fs.existsSync(rutaCarpeta) || !fs.statSync(rutaCarpeta).isDirectory()) {
774
+ opciones.error(
775
+ `wallavi-vercel desplegar: la carpeta no existe: ${opciones.carpeta}`
776
+ );
777
+ return 2;
778
+ }
779
+ const faltan = VARIABLES_VERCEL.filter(
780
+ (nombre) => !opciones.env[nombre]?.trim()
781
+ );
782
+ if (faltan.length > 0) {
783
+ opciones.error(`wallavi-vercel desplegar: faltan ${faltan.join(", ")}`);
784
+ return 2;
785
+ }
786
+ const evaluacion = evaluar({
787
+ carpeta: rutaCarpeta,
788
+ ...opciones.antes !== void 0 ? { antes: opciones.antes } : {},
789
+ env: opciones.env,
790
+ ejecutar: ej
791
+ });
792
+ if (evaluacion.resultado === "sin-cambios") {
793
+ opciones.salida(
794
+ `wallavi-vercel desplegar: sin cambios (${evaluacion.motivo})`
795
+ );
796
+ return 0;
797
+ }
798
+ for (const paso of PASOS_VERCEL) {
799
+ const r = ej("vercel", paso, {
800
+ cwd: rutaCarpeta,
801
+ env: opciones.env
802
+ });
803
+ if (r.salida.trim() !== "") {
804
+ opciones.salida(redactarCredenciales(r.salida.trimEnd(), opciones.env));
805
+ }
806
+ if (r.error.trim() !== "") {
807
+ opciones.error(redactarCredenciales(r.error.trimEnd(), opciones.env));
808
+ }
809
+ if (r.codigo !== 0) {
810
+ const codigo = r.codigo ?? 127;
811
+ opciones.error(
812
+ redactarCredenciales(
813
+ `wallavi-vercel desplegar: vercel ${paso.join(" ")} fall\xF3 (${r.fallo ?? `c\xF3digo ${codigo}`})`,
814
+ opciones.env
815
+ )
816
+ );
817
+ return codigo;
818
+ }
819
+ }
820
+ opciones.salida(
821
+ `wallavi-vercel desplegar: desplegado (${evaluacion.motivo})`
822
+ );
823
+ return 0;
824
+ }
825
+ function comandoDesplegar(args, entorno) {
826
+ const leido = leerArgumentosDesplegar(args);
827
+ if (!leido.ok) {
828
+ entorno.error(`wallavi-vercel desplegar: ${leido.motivo}`);
829
+ return 2;
830
+ }
831
+ return ejecutarDesplegar({
832
+ ...entorno,
833
+ carpeta: leido.valor.carpeta,
834
+ ...leido.valor.antes !== void 0 ? { antes: leido.valor.antes } : {}
835
+ });
836
+ }
837
+ var desplegar = comandoDesplegar;
838
+ var RAMA_PRINCIPAL = "main";
839
+ var PREFIJO_PREVIEW = "preview/";
840
+ function ramaPermitida(rama, config) {
841
+ return rama === RAMA_PRINCIPAL || rama.startsWith(PREFIJO_PREVIEW) || config.ramasConPreview.includes(rama);
842
+ }
843
+ function ejecutarIgnorar(opciones) {
844
+ const ej = opciones.ejecutar ?? ejecutar;
845
+ const evaluar = opciones.evaluar ?? evaluarCambios;
846
+ const decidir = (codigo, motivo) => {
847
+ opciones.salida(
848
+ `wallavi-vercel ignorar: ${codigo === 0 ? "no compilar" : "compilar"} (${motivo})`
849
+ );
850
+ return codigo;
851
+ };
852
+ const rama = opciones.env.VERCEL_GIT_COMMIT_REF?.trim() ?? "";
853
+ if (!rama) {
854
+ return decidir(1, "falta VERCEL_GIT_COMMIT_REF");
855
+ }
856
+ let config = opciones.config;
857
+ if (!config) {
858
+ const carpeta = path3__namespace.resolve(opciones.cwd);
859
+ const ubicacion = ubicarEnRepo(carpeta, ej);
860
+ if (!ubicacion) {
861
+ return decidir(1, `${carpeta} no est\xE1 en un repo de Git`);
862
+ }
863
+ try {
864
+ config = leerConfig(path3__namespace.join(ubicacion.raiz, ARCHIVO_CONFIG));
865
+ } catch (e) {
866
+ return decidir(1, e.message);
867
+ }
868
+ }
869
+ if (!ramaPermitida(rama, config)) {
870
+ return decidir(0, `rama ${rama} fuera de las permitidas`);
871
+ }
872
+ const shaActual = opciones.env.VERCEL_GIT_COMMIT_SHA?.trim() ?? "";
873
+ if (!shaActual) {
874
+ return decidir(1, "falta VERCEL_GIT_COMMIT_SHA");
875
+ }
876
+ const shaAnterior = opciones.env.VERCEL_GIT_PREVIOUS_SHA?.trim() ?? "";
877
+ if (!shaAnterior) {
878
+ return decidir(1, "falta VERCEL_GIT_PREVIOUS_SHA");
879
+ }
880
+ const evaluacion = evaluar({
881
+ carpeta: opciones.cwd,
882
+ antes: shaAnterior,
883
+ env: opciones.env,
884
+ config,
885
+ ejecutar: ej
886
+ });
887
+ if (evaluacion.resultado === "sin-cambios") {
888
+ return decidir(0, evaluacion.motivo);
889
+ }
890
+ return decidir(1, evaluacion.motivo);
891
+ }
892
+ function comandoIgnorar(args, entorno) {
893
+ if (args.length > 0) {
894
+ entorno.error(
895
+ `wallavi-vercel ignorar: argumentos no reconocidos: ${args.join(" ")}`
896
+ );
897
+ return 2;
898
+ }
899
+ return ejecutarIgnorar(entorno);
900
+ }
901
+ var ignorar = comandoIgnorar;
902
+
903
+ // src/index.ts
904
+ var USO = `Uso: wallavi-vercel <comando> [opciones]
905
+
906
+ Comandos:
907
+ ignorar decide si Vercel debe omitir el build
908
+ desplegar --carpeta <ruta> [--antes <sha>] compila y despliega a producci\xF3n si hay cambios
909
+ medir --equipo <slug|team_id> --desde <AAAA-MM-DD> --hasta <AAAA-MM-DD> [...] calcula los minutos de build por d\xEDa en UTC
910
+ capturar --equipo <slug|team_id> --desde <AAAA-MM-DD> --hasta <AAAA-MM-DD> ... descarga deployments de Vercel y escribe su captura
911
+
912
+ Opciones:
913
+ -h, --help muestra esta ayuda
914
+ `;
915
+ var COMANDOS = {
916
+ ignorar: comandoIgnorar,
917
+ desplegar: comandoDesplegar,
918
+ medir: comandoMedir,
919
+ capturar: comandoCapturar
920
+ };
921
+ function principal(argv, entorno) {
922
+ const [nombre, ...args] = argv;
923
+ if (nombre === void 0 || nombre === "--help" || nombre === "-h") {
924
+ (nombre === void 0 ? entorno.error : entorno.salida)(USO);
925
+ return nombre === void 0 ? 2 : 0;
926
+ }
927
+ const comando = Object.hasOwn(COMANDOS, nombre) && COMANDOS[nombre];
928
+ if (!comando) {
929
+ entorno.error(`wallavi-vercel: comando desconocido: ${nombre}
930
+ ${USO}`);
931
+ return 2;
932
+ }
933
+ if (args.includes("--help") || args.includes("-h")) {
934
+ entorno.salida(USO);
935
+ return 0;
936
+ }
937
+ return comando(args, entorno);
938
+ }
939
+ if (__require.main === module) {
940
+ process.exitCode = principal(process.argv.slice(2), {
941
+ cwd: process.cwd(),
942
+ env: process.env,
943
+ salida: (t) => console.log(t),
944
+ error: (t) => console.error(t)
945
+ });
946
+ }
947
+
948
+ exports.API_VERCEL_URL = API_VERCEL_URL;
949
+ exports.ARCHIVO_CAPTURA_V6 = ARCHIVO_CAPTURA_V6;
950
+ exports.ARCHIVO_CONFIG = ARCHIVO_CONFIG;
951
+ exports.ARCHIVO_DEPLOYMENTS_V6 = ARCHIVO_DEPLOYMENTS_V6;
952
+ exports.COMANDOS = COMANDOS;
953
+ exports.ESTADOS_DEPLOYMENT = ESTADOS_DEPLOYMENT;
954
+ exports.ErrorContratoVercel = ErrorContratoVercel;
955
+ exports.ErrorRedVercel = ErrorRedVercel;
956
+ exports.LIMITE_PAGINA_VERCEL = LIMITE_PAGINA_VERCEL;
957
+ exports.MAX_PAGINAS_VERCEL = MAX_PAGINAS_VERCEL;
958
+ exports.PASOS_VERCEL = PASOS_VERCEL;
959
+ exports.PREFIJO_PREVIEW = PREFIJO_PREVIEW;
960
+ exports.RAMA_PRINCIPAL = RAMA_PRINCIPAL;
961
+ exports.REFERENCIA_DEPLOYMENTS_V6 = REFERENCIA_DEPLOYMENTS_V6;
962
+ exports.USO = USO;
963
+ exports.VARIABLES_VERCEL = VARIABLES_VERCEL;
964
+ exports.armarCapturaV6 = armarCapturaV6;
965
+ exports.calcularMinutosPorDia = calcularMinutosPorDia;
966
+ exports.capturar = capturar;
967
+ exports.comandoCapturar = comandoCapturar;
968
+ exports.comandoDesplegar = comandoDesplegar;
969
+ exports.comandoIgnorar = comandoIgnorar;
970
+ exports.comandoMedir = comandoMedir;
971
+ exports.comandoTurbo = comandoTurbo;
972
+ exports.configVacia = configVacia;
973
+ exports.consultarDeployments = consultarDeployments;
974
+ exports.desplegar = desplegar;
975
+ exports.ejecutar = ejecutar;
976
+ exports.ejecutarCapturar = ejecutarCapturar;
977
+ exports.ejecutarDesplegar = ejecutarDesplegar;
978
+ exports.ejecutarIgnorar = ejecutarIgnorar;
979
+ exports.ejecutarMedir = ejecutarMedir;
980
+ exports.esquemaArchivoDeployments = esquemaArchivoDeployments;
981
+ exports.esquemaCapturaV6 = esquemaCapturaV6;
982
+ exports.esquemaConfig = esquemaConfig;
983
+ exports.esquemaDeployment = esquemaDeployment;
984
+ exports.esquemaEquipo = esquemaEquipo;
985
+ exports.esquemaEstadoDeployment = esquemaEstadoDeployment;
986
+ exports.esquemaPaginaDeployments = esquemaPaginaDeployments;
987
+ exports.esquemaPaginaTeams = esquemaPaginaTeams;
988
+ exports.esquemaPaginacion = esquemaPaginacion;
989
+ exports.esquemaPedidoCaptura = esquemaPedidoCaptura;
990
+ exports.evaluarCambios = evaluarCambios;
991
+ exports.ignorar = ignorar;
992
+ exports.leerArgumentosCapturar = leerArgumentosCapturar;
993
+ exports.leerArgumentosDesplegar = leerArgumentosDesplegar;
994
+ exports.leerArgumentosMedir = leerArgumentosMedir;
995
+ exports.leerConfig = leerConfig;
996
+ exports.leerFlagsCli = leerFlagsCli;
997
+ exports.leerRespuestaDeployments = leerRespuestaDeployments;
998
+ exports.medir = medir;
999
+ exports.normalizarCarpeta = normalizarCarpeta;
1000
+ exports.paquetesDeTurbo = paquetesDeTurbo;
1001
+ exports.pedirJsonVercel = pedirJsonVercel;
1002
+ exports.principal = principal;
1003
+ exports.proyectoDeCarpeta = proyectoDeCarpeta;
1004
+ exports.ramaPermitida = ramaPermitida;
1005
+ exports.rangoFechasUtc = rangoFechasUtc;
1006
+ exports.redactarToken = redactarToken;
1007
+ exports.resolverEquipo = resolverEquipo;
1008
+ exports.solicitarVercel = solicitarVercel;
1009
+ exports.ubicarEnRepo = ubicarEnRepo;
1010
+ exports.validarConfig = validarConfig;
1011
+ exports.validarFechaIso = validarFechaIso;