@getparallel/parx 0.5.0 → 0.7.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/CHANGELOG.md +15 -0
- package/README.md +11 -5
- package/dist/actualizar.js +2 -1
- package/dist/agente.js +82 -2
- package/dist/api.js +17 -0
- package/dist/cli.js +351 -35
- package/dist/config.js +6 -0
- package/dist/estimacion.js +118 -0
- package/dist/firmas.js +55 -0
- package/dist/marca.js +56 -19
- package/dist/mision.js +4 -3
- package/dist/par.js +355 -0
- package/dist/remoto.js +295 -0
- package/dist/servicio.js +191 -0
- package/dist/tar.js +99 -0
- package/dist/textos.js +68 -7
- package/dist/tui.js +56 -10
- package/dist/ui.js +45 -2
- package/dist/versiones.js +341 -0
- package/dist/vista.js +133 -0
- package/package.json +1 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { carpetaDeConfiguracion, leerAjustes, VERSION } from "./config.js";
|
|
14
|
+
import { comprobacionApagada, esMasNueva, ordenDeActualizar, PAQUETE, PAQUETE_EN_URL, partes, rutaDelCli } from "./actualizar.js";
|
|
15
|
+
import { clavesDeConfianza, firmaValida, integridadCoincide } from "./firmas.js";
|
|
16
|
+
import { escribirFicheros, leerTgz } from "./tar.js";
|
|
17
|
+
export const CARPETA = 'versiones';
|
|
18
|
+
const ESTADO = 'estado.json';
|
|
19
|
+
const MAX_TGZ = 20 * 1024 * 1024;
|
|
20
|
+
const PLAZO_DESCARGA_MS = 60_000;
|
|
21
|
+
const PLAZO_ARRANQUE_MS = 20_000;
|
|
22
|
+
const GUARDAR = 2;
|
|
23
|
+
export function carpetaDeVersiones(dir = carpetaDeConfiguracion()) {
|
|
24
|
+
return join(dir, CARPETA);
|
|
25
|
+
}
|
|
26
|
+
export function leerEstado(dir = carpetaDeConfiguracion()) {
|
|
27
|
+
try {
|
|
28
|
+
const e = JSON.parse(readFileSync(join(carpetaDeVersiones(dir), ESTADO), 'utf8'));
|
|
29
|
+
const candidatas = {};
|
|
30
|
+
for (const [v, c] of Object.entries(e.candidatas ?? {})) {
|
|
31
|
+
if (!partes(v) || !c || typeof c !== 'object')
|
|
32
|
+
continue;
|
|
33
|
+
const x = c;
|
|
34
|
+
candidatas[v] = {
|
|
35
|
+
descargada: typeof x.descargada === 'string' ? x.descargada : '',
|
|
36
|
+
probada: x.probada === true,
|
|
37
|
+
intentos: typeof x.intentos === 'number' && x.intentos >= 0 ? Math.floor(x.intentos) : 0,
|
|
38
|
+
arranca: x.arranca === true,
|
|
39
|
+
anunciada: x.anunciada === true,
|
|
40
|
+
...(typeof x.mala === 'string' ? { mala: x.mala } : {}),
|
|
41
|
+
...(typeof x.novedad === 'string' ? { novedad: x.novedad } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { candidatas };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { candidatas: {} };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function guardarEstado(e, dir = carpetaDeConfiguracion()) {
|
|
51
|
+
try {
|
|
52
|
+
const carpeta = carpetaDeVersiones(dir);
|
|
53
|
+
if (!existsSync(carpeta))
|
|
54
|
+
mkdirSync(carpeta, { recursive: true, mode: 0o700 });
|
|
55
|
+
writeFileSync(join(carpeta, ESTADO), JSON.stringify(e, null, 2), { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function autoactualizarApagada(env = process.env, ajustes = leerAjustes()) {
|
|
61
|
+
const si = (x) => Boolean(x && x !== '0' && x.toLowerCase() !== 'false');
|
|
62
|
+
return comprobacionApagada(env) || si(env.PARX_NO_AUTO_UPDATE) || ajustes.autoactualizar === false;
|
|
63
|
+
}
|
|
64
|
+
export function gestorPermiteAutoactualizar(gestor) {
|
|
65
|
+
return gestor !== 'npx' && gestor !== 'desarrollo';
|
|
66
|
+
}
|
|
67
|
+
export function rutaDeLaVersion(version, dir = carpetaDeConfiguracion()) {
|
|
68
|
+
const carpeta = join(carpetaDeVersiones(dir), version);
|
|
69
|
+
return { carpeta, cli: join(carpeta, 'dist', 'cli.js') };
|
|
70
|
+
}
|
|
71
|
+
export function novedadDelChangelog(texto, version) {
|
|
72
|
+
const re = new RegExp(`^## ${version.replace(/\./g, '\\.')}\\b[^\\n]*\\n([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, 'm');
|
|
73
|
+
const m = re.exec(texto);
|
|
74
|
+
if (!m)
|
|
75
|
+
return null;
|
|
76
|
+
const linea = m[1].split('\n').map((l) => l.trim()).find((l) => /^[-*]\s+\S/.test(l));
|
|
77
|
+
if (!linea)
|
|
78
|
+
return null;
|
|
79
|
+
const limpia = linea
|
|
80
|
+
.replace(/^[-*]\s+/, '')
|
|
81
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
82
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
83
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
84
|
+
.replace(/\s+/g, ' ')
|
|
85
|
+
.trim();
|
|
86
|
+
if (!limpia)
|
|
87
|
+
return null;
|
|
88
|
+
const frase = (/^(.{20,}?\.)\s/.exec(limpia)?.[1] ?? limpia).replace(/[;:,]$/, '');
|
|
89
|
+
if (frase.length <= 140)
|
|
90
|
+
return frase;
|
|
91
|
+
const corte = frase.slice(0, 139);
|
|
92
|
+
const espacio = corte.lastIndexOf(' ');
|
|
93
|
+
return `${(espacio > 80 ? corte.slice(0, espacio) : corte).replace(/[\s;:,.]+$/, '')}…`;
|
|
94
|
+
}
|
|
95
|
+
function registroDe(opts) {
|
|
96
|
+
return (opts.registro ?? opts.env?.PARX_REGISTRY ?? process.env.PARX_REGISTRY ?? 'https://registry.npmjs.org').replace(/\/+$/, '');
|
|
97
|
+
}
|
|
98
|
+
export async function manifiestoDe(version, opts = {}) {
|
|
99
|
+
const registro = registroDe(opts);
|
|
100
|
+
try {
|
|
101
|
+
const r = await (opts.fetch ?? fetch)(`${registro}/${PAQUETE_EN_URL}/${version}`, {
|
|
102
|
+
headers: { accept: 'application/json', 'user-agent': `parx/${VERSION}` },
|
|
103
|
+
signal: AbortSignal.timeout(opts.plazoMs ?? 10_000),
|
|
104
|
+
});
|
|
105
|
+
if (!r.ok)
|
|
106
|
+
return null;
|
|
107
|
+
const j = (await r.json());
|
|
108
|
+
if (j.version !== version || !j.dist || typeof j.dist.integrity !== 'string' || typeof j.dist.tarball !== 'string')
|
|
109
|
+
return null;
|
|
110
|
+
return { version, integrity: j.dist.integrity, tarball: j.dist.tarball, firmas: j.dist.signatures };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function tarballDelRegistro(tarball, registro) {
|
|
117
|
+
try {
|
|
118
|
+
const t = new URL(tarball);
|
|
119
|
+
const r = new URL(registro);
|
|
120
|
+
if (t.host !== r.host)
|
|
121
|
+
return false;
|
|
122
|
+
if (t.protocol !== r.protocol)
|
|
123
|
+
return false;
|
|
124
|
+
return t.protocol === 'https:' || t.hostname === '127.0.0.1' || t.hostname === 'localhost';
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function bajar(url, opts) {
|
|
131
|
+
try {
|
|
132
|
+
const r = await (opts.fetch ?? fetch)(url, { headers: { 'user-agent': `parx/${VERSION}` }, signal: AbortSignal.timeout(opts.plazoMs ?? PLAZO_DESCARGA_MS) });
|
|
133
|
+
if (!r.ok)
|
|
134
|
+
return null;
|
|
135
|
+
const largo = Number(r.headers.get('content-length') ?? 0);
|
|
136
|
+
if (largo > MAX_TGZ)
|
|
137
|
+
return null;
|
|
138
|
+
const b = Buffer.from(await r.arrayBuffer());
|
|
139
|
+
return b.length > MAX_TGZ ? null : b;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function probarQueArranca(cli, version, opts = {}) {
|
|
146
|
+
const env = { ...(opts.env ?? process.env), PARX_LANZADO_DESDE: 'prueba', PARX_NO_UPDATE_CHECK: '1' };
|
|
147
|
+
delete env.PARX_PRUEBAS;
|
|
148
|
+
return new Promise((resolver) => {
|
|
149
|
+
execFile(opts.node ?? process.execPath, [cli, '--version'], { env, timeout: opts.plazoMs ?? PLAZO_ARRANQUE_MS, windowsHide: true }, (error, stdout) => {
|
|
150
|
+
resolver(!error && String(stdout).trim() === `parx ${version}`);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
export async function prepararVersion(version, opts = {}) {
|
|
155
|
+
const dir = opts.dir ?? carpetaDeConfiguracion();
|
|
156
|
+
if (!partes(version))
|
|
157
|
+
return { ok: false, motivo: 'manifiesto' };
|
|
158
|
+
const estado = leerEstado(dir);
|
|
159
|
+
const ya = estado.candidatas[version];
|
|
160
|
+
const { carpeta, cli } = rutaDeLaVersion(version, dir);
|
|
161
|
+
if (ya?.probada && !ya.mala && existsSync(cli))
|
|
162
|
+
return { ok: true, version, novedad: ya.novedad ?? null, yaEstaba: true };
|
|
163
|
+
const registro = registroDe(opts);
|
|
164
|
+
const m = await manifiestoDe(version, opts);
|
|
165
|
+
if (!m)
|
|
166
|
+
return { ok: false, motivo: 'manifiesto' };
|
|
167
|
+
if (!tarballDelRegistro(m.tarball, registro))
|
|
168
|
+
return { ok: false, motivo: 'manifiesto' };
|
|
169
|
+
const claves = opts.claves ?? clavesDeConfianza(opts.env ?? process.env);
|
|
170
|
+
if (!firmaValida({ nombre: PAQUETE, version, integrity: m.integrity, firmas: m.firmas }, claves))
|
|
171
|
+
return { ok: false, motivo: 'firma' };
|
|
172
|
+
const tgz = await bajar(m.tarball, opts);
|
|
173
|
+
if (!tgz)
|
|
174
|
+
return { ok: false, motivo: 'descarga' };
|
|
175
|
+
if (!integridadCoincide(tgz, m.integrity))
|
|
176
|
+
return { ok: false, motivo: 'integridad' };
|
|
177
|
+
let ficheros;
|
|
178
|
+
try {
|
|
179
|
+
ficheros = leerTgz(tgz);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return { ok: false, motivo: 'contenido' };
|
|
183
|
+
}
|
|
184
|
+
const pkg = ficheros.find((f) => f.ruta === 'package.json');
|
|
185
|
+
let manifiesto = {};
|
|
186
|
+
try {
|
|
187
|
+
manifiesto = pkg ? JSON.parse(pkg.contenido.toString('utf8')) : {};
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
}
|
|
191
|
+
if (manifiesto.name !== PAQUETE || manifiesto.version !== version || !ficheros.some((f) => f.ruta === 'dist/cli.js'))
|
|
192
|
+
return { ok: false, motivo: 'contenido' };
|
|
193
|
+
const temporal = `${carpeta}.tmp`;
|
|
194
|
+
try {
|
|
195
|
+
rmSync(temporal, { recursive: true, force: true });
|
|
196
|
+
rmSync(carpeta, { recursive: true, force: true });
|
|
197
|
+
mkdirSync(temporal, { recursive: true, mode: 0o700 });
|
|
198
|
+
escribirFicheros(ficheros, temporal);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
rmSync(temporal, { recursive: true, force: true });
|
|
202
|
+
return { ok: false, motivo: 'disco' };
|
|
203
|
+
}
|
|
204
|
+
const arranca = await probarQueArranca(join(temporal, 'dist', 'cli.js'), version, { node: opts.node, env: opts.env });
|
|
205
|
+
if (!arranca) {
|
|
206
|
+
rmSync(temporal, { recursive: true, force: true });
|
|
207
|
+
const e = leerEstado(dir);
|
|
208
|
+
e.candidatas[version] = { descargada: new Date(opts.ahora ?? Date.now()).toISOString(), probada: false, intentos: 0, arranca: false, anunciada: false, mala: 'no_arranca' };
|
|
209
|
+
guardarEstado(e, dir);
|
|
210
|
+
return { ok: false, motivo: 'no_arranca' };
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
renameSync(temporal, carpeta);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
rmSync(temporal, { recursive: true, force: true });
|
|
217
|
+
return { ok: false, motivo: 'disco' };
|
|
218
|
+
}
|
|
219
|
+
const changelog = ficheros.find((f) => f.ruta === 'CHANGELOG.md');
|
|
220
|
+
const novedad = changelog ? novedadDelChangelog(changelog.contenido.toString('utf8'), version) : null;
|
|
221
|
+
const e = leerEstado(dir);
|
|
222
|
+
e.candidatas[version] = { descargada: new Date(opts.ahora ?? Date.now()).toISOString(), probada: true, intentos: 0, arranca: false, anunciada: false, ...(novedad ? { novedad } : {}) };
|
|
223
|
+
guardarEstado(e, dir);
|
|
224
|
+
limpiarVersiones(dir);
|
|
225
|
+
return { ok: true, version, novedad, yaEstaba: false };
|
|
226
|
+
}
|
|
227
|
+
export function limpiarVersiones(dir = carpetaDeConfiguracion()) {
|
|
228
|
+
const e = leerEstado(dir);
|
|
229
|
+
const carpeta = carpetaDeVersiones(dir);
|
|
230
|
+
const buenas = Object.entries(e.candidatas)
|
|
231
|
+
.filter(([, c]) => c.probada && !c.mala)
|
|
232
|
+
.map(([v]) => v)
|
|
233
|
+
.sort((a, b) => (esMasNueva(a, b) ? -1 : esMasNueva(b, a) ? 1 : 0));
|
|
234
|
+
const quedarse = new Set(buenas.slice(0, GUARDAR));
|
|
235
|
+
let entradas = [];
|
|
236
|
+
try {
|
|
237
|
+
entradas = readdirSync(carpeta);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
for (const nombre of entradas) {
|
|
243
|
+
if (nombre === ESTADO)
|
|
244
|
+
continue;
|
|
245
|
+
if (!quedarse.has(nombre)) {
|
|
246
|
+
rmSync(join(carpeta, nombre), { recursive: true, force: true });
|
|
247
|
+
if (e.candidatas[nombre] && !e.candidatas[nombre].mala)
|
|
248
|
+
delete e.candidatas[nombre];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
guardarEstado(e, dir);
|
|
252
|
+
}
|
|
253
|
+
export function marcarMala(version, motivo, dir = carpetaDeConfiguracion()) {
|
|
254
|
+
const e = leerEstado(dir);
|
|
255
|
+
const c = e.candidatas[version];
|
|
256
|
+
if (c)
|
|
257
|
+
c.mala = motivo;
|
|
258
|
+
else
|
|
259
|
+
e.candidatas[version] = { descargada: '', probada: false, intentos: 0, arranca: false, anunciada: false, mala: motivo };
|
|
260
|
+
guardarEstado(e, dir);
|
|
261
|
+
rmSync(rutaDeLaVersion(version, dir).carpeta, { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
export function elegirVersionParaArrancar(opts = {}) {
|
|
264
|
+
const dir = opts.dir ?? carpetaDeConfiguracion();
|
|
265
|
+
const actual = opts.actual ?? VERSION;
|
|
266
|
+
const e = leerEstado(dir);
|
|
267
|
+
let retirada = null;
|
|
268
|
+
let cambios = false;
|
|
269
|
+
for (const [v, c] of Object.entries(e.candidatas)) {
|
|
270
|
+
if (!esMasNueva(v, actual)) {
|
|
271
|
+
rmSync(rutaDeLaVersion(v, dir).carpeta, { recursive: true, force: true });
|
|
272
|
+
delete e.candidatas[v];
|
|
273
|
+
cambios = true;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (c.probada && !c.mala && !c.arranca && c.intentos >= 2) {
|
|
277
|
+
c.mala = 'no_confirmo';
|
|
278
|
+
rmSync(rutaDeLaVersion(v, dir).carpeta, { recursive: true, force: true });
|
|
279
|
+
retirada = v;
|
|
280
|
+
cambios = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const validas = Object.entries(e.candidatas)
|
|
284
|
+
.filter(([v, c]) => c.probada && !c.mala && existsSync(rutaDeLaVersion(v, dir).cli))
|
|
285
|
+
.map(([v]) => v)
|
|
286
|
+
.sort((a, b) => (esMasNueva(a, b) ? -1 : 1));
|
|
287
|
+
const version = validas[0];
|
|
288
|
+
if (version) {
|
|
289
|
+
e.candidatas[version].intentos++;
|
|
290
|
+
cambios = true;
|
|
291
|
+
}
|
|
292
|
+
if (cambios)
|
|
293
|
+
guardarEstado(e, dir);
|
|
294
|
+
return { elegida: version ? { version, cli: rutaDeLaVersion(version, dir).cli } : null, retirada };
|
|
295
|
+
}
|
|
296
|
+
export function confirmarArranque(opts = {}) {
|
|
297
|
+
const env = opts.env ?? process.env;
|
|
298
|
+
const desde = env.PARX_LANZADO_DESDE ?? '';
|
|
299
|
+
if (!partes(desde))
|
|
300
|
+
return null;
|
|
301
|
+
const dir = opts.dir ?? carpetaDeConfiguracion();
|
|
302
|
+
const actual = opts.actual ?? VERSION;
|
|
303
|
+
const e = leerEstado(dir);
|
|
304
|
+
const c = e.candidatas[actual];
|
|
305
|
+
if (!c)
|
|
306
|
+
return null;
|
|
307
|
+
const primera = !c.anunciada;
|
|
308
|
+
c.arranca = true;
|
|
309
|
+
c.intentos = 0;
|
|
310
|
+
c.anunciada = true;
|
|
311
|
+
guardarEstado(e, dir);
|
|
312
|
+
return primera ? { version: actual, novedad: c.novedad ?? null } : null;
|
|
313
|
+
}
|
|
314
|
+
export async function lanzarLaMasNueva(opts = {}) {
|
|
315
|
+
const env = opts.env ?? process.env;
|
|
316
|
+
if (env.PARX_LANZADO_DESDE)
|
|
317
|
+
return { lanzada: false, retirada: null };
|
|
318
|
+
if (autoactualizarApagada(env, opts.ajustes ?? leerAjustes(opts.dir)))
|
|
319
|
+
return { lanzada: false, retirada: null };
|
|
320
|
+
const gestor = opts.gestor ?? ordenDeActualizar(rutaDelCli()).gestor;
|
|
321
|
+
if (!gestorPermiteAutoactualizar(gestor))
|
|
322
|
+
return { lanzada: false, retirada: null };
|
|
323
|
+
let retirada = null;
|
|
324
|
+
for (let intento = 0; intento < 3; intento++) {
|
|
325
|
+
const eleccion = elegirVersionParaArrancar({ dir: opts.dir, actual: opts.actual });
|
|
326
|
+
retirada = eleccion.retirada ?? retirada;
|
|
327
|
+
if (!eleccion.elegida)
|
|
328
|
+
return { lanzada: false, retirada };
|
|
329
|
+
env.PARX_LANZADO_DESDE = opts.actual ?? VERSION;
|
|
330
|
+
try {
|
|
331
|
+
await (opts.importar ?? ((u) => import(__rewriteRelativeImportExtension(u))))(pathToFileURL(eleccion.elegida.cli).href);
|
|
332
|
+
return { lanzada: true, version: eleccion.elegida.version };
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
delete env.PARX_LANZADO_DESDE;
|
|
336
|
+
marcarMala(eleccion.elegida.version, `import: ${e instanceof Error ? e.message.slice(0, 200) : String(e)}`, opts.dir);
|
|
337
|
+
retirada = eleccion.elegida.version;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { lanzada: false, retirada };
|
|
341
|
+
}
|
package/dist/vista.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { join, relative, sep } from 'node:path';
|
|
4
|
+
import { subirVistaRemota } from "./api.js";
|
|
5
|
+
export const EXTENSIONES = ['html', 'htm', 'css', 'js', 'mjs', 'json', 'webmanifest', 'txt', 'xml', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'ico', 'woff', 'woff2', 'ttf', 'otf', 'mp4', 'webm', 'mp3', 'wav', 'map'];
|
|
6
|
+
const EXT = new Set(EXTENSIONES);
|
|
7
|
+
const SALTAR = new Set(['node_modules', '.git', '.next', '.turbo', '.cache', 'coverage', '__pycache__', '.venv', 'venv', 'target']);
|
|
8
|
+
export const MAX_ARCHIVOS = 300;
|
|
9
|
+
export const MAX_ARCHIVO = 5 * 1024 * 1024;
|
|
10
|
+
export const MAX_TOTAL = 25 * 1024 * 1024;
|
|
11
|
+
const LOTE = 2_300_000;
|
|
12
|
+
export function raizWeb(raiz) {
|
|
13
|
+
for (const c of ['dist', 'build', 'out', '.', 'public', 'docs', 'site']) {
|
|
14
|
+
const abs = c === '.' ? raiz : join(raiz, c);
|
|
15
|
+
try {
|
|
16
|
+
if (existsSync(join(abs, 'index.html')) && statSync(join(abs, 'index.html')).isFile())
|
|
17
|
+
return { abs, nombre: c };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function recogerWeb(abs) {
|
|
25
|
+
const out = [];
|
|
26
|
+
let total = 0;
|
|
27
|
+
const recorrer = (dir, profundidad) => {
|
|
28
|
+
if (profundidad > 8 || out.length >= MAX_ARCHIVOS)
|
|
29
|
+
return;
|
|
30
|
+
let entradas;
|
|
31
|
+
try {
|
|
32
|
+
entradas = readdirSync(dir).sort();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const n of entradas) {
|
|
38
|
+
if (out.length >= MAX_ARCHIVOS)
|
|
39
|
+
return;
|
|
40
|
+
if (n.startsWith('.') || SALTAR.has(n))
|
|
41
|
+
continue;
|
|
42
|
+
const ruta = join(dir, n);
|
|
43
|
+
let st;
|
|
44
|
+
try {
|
|
45
|
+
st = statSync(ruta);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (st.isDirectory()) {
|
|
51
|
+
recorrer(ruta, profundidad + 1);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const ext = (/\.([a-z0-9]+)$/i.exec(n)?.[1] ?? '').toLowerCase();
|
|
55
|
+
if (!st.isFile() || !EXT.has(ext) || st.size > MAX_ARCHIVO || total + st.size > MAX_TOTAL)
|
|
56
|
+
continue;
|
|
57
|
+
const rel = relative(abs, ruta).split(sep).join('/');
|
|
58
|
+
if (!/^[A-Za-z0-9._\-/@+ ()]+$/.test(rel) || rel.length > 200)
|
|
59
|
+
continue;
|
|
60
|
+
const datos = readFileSync(ruta);
|
|
61
|
+
total += st.size;
|
|
62
|
+
out.push({ ruta: rel, abs: ruta, hash: createHash('sha256').update(datos).digest('hex'), tam: st.size });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
recorrer(abs, 0);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
export function enTandas(lista, max = LOTE) {
|
|
69
|
+
const tandas = [];
|
|
70
|
+
let actual = [];
|
|
71
|
+
let peso = 0;
|
|
72
|
+
for (const a of lista) {
|
|
73
|
+
if (actual.length && peso + a.tam > max) {
|
|
74
|
+
tandas.push(actual);
|
|
75
|
+
actual = [];
|
|
76
|
+
peso = 0;
|
|
77
|
+
}
|
|
78
|
+
actual.push(a);
|
|
79
|
+
peso += a.tam;
|
|
80
|
+
}
|
|
81
|
+
tandas.push(actual);
|
|
82
|
+
return tandas;
|
|
83
|
+
}
|
|
84
|
+
export class VistaPrevia {
|
|
85
|
+
token = randomBytes(32).toString('base64url');
|
|
86
|
+
subirLote;
|
|
87
|
+
constructor(subirLote = subirVistaRemota) {
|
|
88
|
+
this.subirLote = subirLote;
|
|
89
|
+
}
|
|
90
|
+
subidos = new Map();
|
|
91
|
+
url = null;
|
|
92
|
+
version = 0;
|
|
93
|
+
enMarcha = null;
|
|
94
|
+
async actualizar(o) {
|
|
95
|
+
while (this.enMarcha)
|
|
96
|
+
await this.enMarcha.catch(() => undefined);
|
|
97
|
+
const p = this.subir(o);
|
|
98
|
+
this.enMarcha = p;
|
|
99
|
+
try {
|
|
100
|
+
return await p;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.enMarcha = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async subir(o) {
|
|
107
|
+
const web = raizWeb(o.raiz);
|
|
108
|
+
if (!web)
|
|
109
|
+
return null;
|
|
110
|
+
const lista = recogerWeb(web.abs);
|
|
111
|
+
if (!lista.some((a) => a.ruta === 'index.html'))
|
|
112
|
+
return null;
|
|
113
|
+
const cambiados = lista.filter((a) => this.subidos.get(a.ruta) !== a.hash);
|
|
114
|
+
const iguales = cambiados.length === 0 && lista.length === this.subidos.size;
|
|
115
|
+
if (iguales && this.url)
|
|
116
|
+
return { url: this.url, version: this.version, cambiados: 0, carpeta: web.nombre };
|
|
117
|
+
const tandas = enTandas(cambiados);
|
|
118
|
+
for (let i = 0; i < tandas.length; i++) {
|
|
119
|
+
const ultima = i === tandas.length - 1;
|
|
120
|
+
const archivos = tandas[i].map((a) => ({ ruta: a.ruta, datos: readFileSync(a.abs).toString('base64') }));
|
|
121
|
+
const r = await this.subirLote(o.servidor, o.token, o.remoto, {
|
|
122
|
+
archivos,
|
|
123
|
+
...(ultima ? { raiz: web.nombre, token: this.token, lista: lista.map(({ ruta, hash, tam }) => ({ ruta, hash, tam })) } : {}),
|
|
124
|
+
});
|
|
125
|
+
if (ultima) {
|
|
126
|
+
this.url = r.url;
|
|
127
|
+
this.version = r.version;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.subidos = new Map(lista.map((a) => [a.ruta, a.hash]));
|
|
131
|
+
return { url: this.url, version: this.version, cambiados: cambiados.length, carpeta: web.nombre };
|
|
132
|
+
}
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getparallel/parx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "parx: the coding agent of Parallel in your terminal. Reads and edits your project, runs commands and tests, and does coding tasks end to end. Mac, Windows and Linux.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"parx",
|