Compare commits
9 Commits
aa04270550
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5342fb489d | |||
| c42814f963 | |||
| 0d1de7f7e2 | |||
| b34433a71e | |||
| 492d844523 | |||
| 8237e38164 | |||
| e04be61952 | |||
| 1b7e4f36e9 | |||
| d8cc6e9613 |
@@ -1,6 +1,9 @@
|
|||||||
# Ignorar los directorios de dependencias
|
# Ignorar los directorios de dependencias
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# Ignorar los volumenes respaldados
|
||||||
|
docker-volumes*
|
||||||
|
|
||||||
# Ignorar las carpetas de bases de datos
|
# Ignorar las carpetas de bases de datos
|
||||||
.db/
|
.db/
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Cargar Comanda</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Formulario de Carga de Comanda</h2>
|
|
||||||
|
|
||||||
<form id="formComanda">
|
|
||||||
<label for="mesaSelect">Mesa:</label>
|
|
||||||
<select id="mesaSelect" name="mesa_id" required></select>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<label for="productoSelect">Producto:</label>
|
|
||||||
<select id="productosSelect"></select>
|
|
||||||
|
|
||||||
<label for="cantidadInput">Cantidad:</label>
|
|
||||||
<input type="number" id="cantidadInput" min="1" value="1">
|
|
||||||
<button type="button" onclick="agregarProducto()">Agregar</button>
|
|
||||||
|
|
||||||
<ul id="listaProductos"></ul>
|
|
||||||
|
|
||||||
<input type="hidden" name="productos" id="productosJSON">
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<button type="submit">Guardar Comanda</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Cargar categorías y mesas (productos se maneja distinto por el precio)
|
|
||||||
async function cargarSelect(endpoint, selectId, mostrar) {
|
|
||||||
const res = await fetch(endpoint);
|
|
||||||
const data = await res.json();
|
|
||||||
const select = document.getElementById(selectId);
|
|
||||||
data.forEach(item => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = item.id;
|
|
||||||
option.textContent = mostrar(item);
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function cargarProductosConPrecio() {
|
|
||||||
const res = await fetch('/api/obtenerProductos');
|
|
||||||
const productos = await res.json();
|
|
||||||
const select = document.getElementById('productosSelect');
|
|
||||||
productos.forEach(prod => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = prod.id;
|
|
||||||
option.textContent = `${prod.nombre} ($${prod.precio})`;
|
|
||||||
option.setAttribute('data-precio', prod.precio);
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const listaProductos = [];
|
|
||||||
function agregarProducto() {
|
|
||||||
const select = document.getElementById('productosSelect');
|
|
||||||
const cantidad = parseInt(document.getElementById('cantidadInput').value);
|
|
||||||
const productoId = select.value;
|
|
||||||
const nombre = select.options[select.selectedIndex].textContent;
|
|
||||||
const precioUnitario = parseFloat(select.options[select.selectedIndex].dataset.precio);
|
|
||||||
|
|
||||||
if (!productoId || isNaN(cantidad) || cantidad <= 0) {
|
|
||||||
alert("Producto o cantidad inválida");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
listaProductos.push({
|
|
||||||
producto_id: productoId,
|
|
||||||
cantidad,
|
|
||||||
precio_unitario: precioUnitario
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mostrar en lista visual
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.textContent = `${nombre} x${cantidad} - $${(precioUnitario * cantidad).toFixed(2)}`;
|
|
||||||
document.getElementById('listaProductos').appendChild(li);
|
|
||||||
|
|
||||||
// Actualizar JSON oculto
|
|
||||||
document.getElementById('productosJSON').value = JSON.stringify(listaProductos);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('formComanda').addEventListener('submit', async function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const mesaId = document.getElementById('mesaSelect').value;
|
|
||||||
const productos = listaProductos;
|
|
||||||
|
|
||||||
if (!mesaId || productos.length === 0) {
|
|
||||||
alert('Selecciona una mesa y al menos un producto');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch('/api/cargarComandas', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
mesa_id: mesaId,
|
|
||||||
productos: productos
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const resultado = await res.json();
|
|
||||||
alert(resultado.mensaje || 'Comanda cargada correctamente');
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
cargarSelect('/api/obtenerMesas', 'mesaSelect', mesa => `Mesa ${mesa.numero}`);
|
|
||||||
cargarProductosConPrecio();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Crear comanda</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Crear comanda</h1>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<label>
|
|
||||||
Mesa (ID o número):
|
|
||||||
<input type="number" id="mesaId" required />
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label>
|
|
||||||
Mozo (ID de usuario):
|
|
||||||
<input type="text" id="mozoId" required />
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label>
|
|
||||||
Notas:
|
|
||||||
<input type="text" id="notas" placeholder="Sin observaciones" />
|
|
||||||
</label>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Agregar productos</h2>
|
|
||||||
<label>
|
|
||||||
Producto:
|
|
||||||
<select id="productoSelect"></select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Cantidad:
|
|
||||||
<input type="number" id="cantidadInput" value="1" min="1" />
|
|
||||||
</label>
|
|
||||||
<button id="agregarBtn">Agregar</button>
|
|
||||||
|
|
||||||
<h3>Items de la comanda</h3>
|
|
||||||
<ul id="itemsList"></ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<button id="enviarBtn">Enviar comanda</button>
|
|
||||||
<pre id="salida"></pre>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// === CONFIGURA AQUÍ SI ES NECESARIO ===
|
|
||||||
const API_BASE = "http://localhost:3000"; // Cambia al puerto/host de tu servidor Node
|
|
||||||
const PRODUCTOS_PATH = "/productos"; // GET
|
|
||||||
const COMANDAS_PATH = "/comandas"; // POST
|
|
||||||
|
|
||||||
// === ESTADO EN MEMORIA ===
|
|
||||||
const productosCache = new Map(); // id -> {id, nombre, ...}
|
|
||||||
const items = []; // {producto_id, cantidad}
|
|
||||||
|
|
||||||
// === ELEMENTOS DEL DOM ===
|
|
||||||
const productoSelect = document.getElementById("productoSelect");
|
|
||||||
const cantidadInput = document.getElementById("cantidadInput");
|
|
||||||
const agregarBtn = document.getElementById("agregarBtn");
|
|
||||||
const itemsList = document.getElementById("itemsList");
|
|
||||||
const enviarBtn = document.getElementById("enviarBtn");
|
|
||||||
const mesaIdInput = document.getElementById("mesaId");
|
|
||||||
const mozoIdInput = document.getElementById("mozoId");
|
|
||||||
const notasInput = document.getElementById("notas");
|
|
||||||
const salida = document.getElementById("salida");
|
|
||||||
|
|
||||||
// === UTILIDADES ===
|
|
||||||
function renderItems() {
|
|
||||||
itemsList.innerHTML = "";
|
|
||||||
items.forEach((it, idx) => {
|
|
||||||
const li = document.createElement("li");
|
|
||||||
const prod = productosCache.get(it.producto_id);
|
|
||||||
const nombre = prod ? (prod.nombre || prod.name || `Producto ${it.producto_id}`) : `ID ${it.producto_id}`;
|
|
||||||
li.textContent = `${nombre} × ${it.cantidad}`;
|
|
||||||
const btn = document.createElement("button");
|
|
||||||
btn.textContent = "Quitar";
|
|
||||||
btn.onclick = () => {
|
|
||||||
items.splice(idx, 1);
|
|
||||||
renderItems();
|
|
||||||
};
|
|
||||||
li.appendChild(document.createTextNode(" "));
|
|
||||||
li.appendChild(btn);
|
|
||||||
itemsList.appendChild(li);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function mostrarMensaje(obj) {
|
|
||||||
try {
|
|
||||||
salida.textContent = typeof obj === "string" ? obj : JSON.stringify(obj, null, 2);
|
|
||||||
} catch {
|
|
||||||
salida.textContent = String(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validarEnteroPositivo(valor) {
|
|
||||||
const n = Number(valor);
|
|
||||||
return Number.isInteger(n) && n > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// === LOGICA ===
|
|
||||||
async function cargarProductos() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(API_BASE + PRODUCTOS_PATH);
|
|
||||||
if (!res.ok) throw new Error("No se pudieron obtener los productos");
|
|
||||||
const data = await res.json();
|
|
||||||
// Espera un array de productos con al menos {id, nombre}
|
|
||||||
productoSelect.innerHTML = "";
|
|
||||||
data.forEach(p => {
|
|
||||||
productosCache.set(p.id, p);
|
|
||||||
const opt = document.createElement("option");
|
|
||||||
opt.value = p.id;
|
|
||||||
opt.textContent = p.nombre || p.name || `Producto ${p.id}`;
|
|
||||||
productoSelect.appendChild(opt);
|
|
||||||
});
|
|
||||||
if (data.length === 0) {
|
|
||||||
mostrarMensaje("No hay productos disponibles.");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
mostrarMensaje("Error cargando productos: " + e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
agregarBtn.addEventListener("click", () => {
|
|
||||||
const prodId = Number(productoSelect.value);
|
|
||||||
const cant = Number(cantidadInput.value);
|
|
||||||
if (!validarEnteroPositivo(prodId)) {
|
|
||||||
return mostrarMensaje("Selecciona un producto válido.");
|
|
||||||
}
|
|
||||||
if (!validarEnteroPositivo(cant)) {
|
|
||||||
return mostrarMensaje("La cantidad debe ser un entero positivo.");
|
|
||||||
}
|
|
||||||
// Si ya existe el producto en la lista, acumula cantidad
|
|
||||||
const existente = items.find(i => i.producto_id === prodId);
|
|
||||||
if (existente) {
|
|
||||||
existente.cantidad += cant;
|
|
||||||
} else {
|
|
||||||
items.push({ producto_id: prodId, cantidad: cant });
|
|
||||||
}
|
|
||||||
renderItems();
|
|
||||||
cantidadInput.value = 1;
|
|
||||||
mostrarMensaje("Producto agregado.");
|
|
||||||
});
|
|
||||||
|
|
||||||
enviarBtn.addEventListener("click", async () => {
|
|
||||||
const mesa_id = Number(mesaIdInput.value);
|
|
||||||
const mozo_id = mozoIdInput.value.trim();
|
|
||||||
const notas = notasInput.value.trim();
|
|
||||||
|
|
||||||
if (!validarEnteroPositivo(mesa_id)) {
|
|
||||||
return mostrarMensaje("Debes indicar un número de mesa válido.");
|
|
||||||
}
|
|
||||||
if (!mozo_id) {
|
|
||||||
return mostrarMensaje("Debes indicar el ID del mozo.");
|
|
||||||
}
|
|
||||||
if (items.length === 0) {
|
|
||||||
return mostrarMensaje("Agrega al menos un producto a la comanda.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = { mesa_id, mozo_id, notas, items };
|
|
||||||
|
|
||||||
enviarBtn.disabled = true;
|
|
||||||
mostrarMensaje("Enviando comanda...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(API_BASE + COMANDAS_PATH, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
if (!res.ok) {
|
|
||||||
mostrarMensaje({ error: "No se pudo crear la comanda", detalle: data });
|
|
||||||
} else {
|
|
||||||
mostrarMensaje({ ok: true, comanda: data });
|
|
||||||
// Limpia el estado
|
|
||||||
items.length = 0;
|
|
||||||
renderItems();
|
|
||||||
notasInput.value = "";
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
mostrarMensaje("Error al enviar comanda: " + e.message);
|
|
||||||
} finally {
|
|
||||||
enviarBtn.disabled = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cargar productos al iniciar
|
|
||||||
cargarProductos();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Dashboard</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Mesas</h2>
|
|
||||||
<select id="mesasSelect"></select>
|
|
||||||
|
|
||||||
<h2>Productos</h2>
|
|
||||||
<select id="productosSelect"></select>
|
|
||||||
|
|
||||||
<h2>Categorías</h2>
|
|
||||||
<select id="categoriasSelect"></select>
|
|
||||||
|
|
||||||
<h2>Comandas</h2>
|
|
||||||
<select id="comandasSelect"></select>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function cargarDatos(endpoint, selectId, mostrar) {
|
|
||||||
const res = await fetch(endpoint); // Usar endpoint relativo
|
|
||||||
const data = await res.json();
|
|
||||||
const select = document.getElementById(selectId);
|
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = item.id;
|
|
||||||
option.textContent = mostrar(item);
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Al cargar la página, cargamos los datos
|
|
||||||
window.onload = () => {
|
|
||||||
cargarDatos('api/obtenerMesas', 'mesasSelect', mesa => `Mesa ${mesa.numero}`);
|
|
||||||
cargarDatos('api/obtenerProductos', 'productosSelect', prod => `${prod.nombre} ($${prod.precio})`);
|
|
||||||
cargarDatos('api/obtenerCategorias', 'categoriasSelect', cat => cat.nombre);
|
|
||||||
cargarDatos('api/obtenerComandas', 'comandasSelect', com => `Comanda ${com.id} - Mesa ${com.mesa_id} - $${com.total}`);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,633 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import List, Dict, Tuple, Optional, Set
|
||||||
|
|
||||||
|
PROJECT_ROOT = pathlib.Path.cwd()
|
||||||
|
COMPOSE_BASE = PROJECT_ROOT / "compose.yaml"
|
||||||
|
COMPOSE_DEV = PROJECT_ROOT / "compose.dev.yaml"
|
||||||
|
COMPOSE_PROD = PROJECT_ROOT / "compose.prod.yaml"
|
||||||
|
COMPOSE_NPM = PROJECT_ROOT / "compose.npm.yaml"
|
||||||
|
COMPOSE_DBVR = PROJECT_ROOT / "compose.dbeaver.yaml"
|
||||||
|
|
||||||
|
GLOBAL_DEFAULT_PROJECT = "suitecoffee" # proyecto global (NPM/DBeaver)
|
||||||
|
|
||||||
|
# ---------- Shell utils ----------
|
||||||
|
|
||||||
|
def run(cmd: List[str], check=True, capture_output=True, text=True) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run(cmd, check=check, capture_output=capture_output, text=text)
|
||||||
|
|
||||||
|
def which(program: str) -> bool:
|
||||||
|
from shutil import which as _which
|
||||||
|
return _which(program) is not None
|
||||||
|
|
||||||
|
# ---------- Docker volume discovery ----------
|
||||||
|
|
||||||
|
def docker_volume_ls_json(filters: List[str]) -> List[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
Devuelve objetos de 'docker volume ls' (formato json por entrada).
|
||||||
|
Soporta filtros como '--filter label=...'.
|
||||||
|
"""
|
||||||
|
cmd = ["docker", "volume", "ls", "--format", "{{json .}}"]
|
||||||
|
for f in filters:
|
||||||
|
cmd += ["--filter", f]
|
||||||
|
try:
|
||||||
|
cp = run(cmd)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return []
|
||||||
|
out = []
|
||||||
|
for line in cp.stdout.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
out.append(json.loads(line))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
def docker_volume_ls_names(filters: List[str]) -> List[str]:
|
||||||
|
"""Devuelve solo los nombres (Name) con filtros aplicados."""
|
||||||
|
rows = docker_volume_ls_json(filters)
|
||||||
|
names = []
|
||||||
|
for v in rows:
|
||||||
|
name = v.get("Name")
|
||||||
|
if name:
|
||||||
|
names.append(name)
|
||||||
|
return names
|
||||||
|
|
||||||
|
def list_by_label_project(project: str) -> List[Dict[str, str]]:
|
||||||
|
return docker_volume_ls_json([f"label=com.docker.compose.project={project}"])
|
||||||
|
|
||||||
|
def list_by_name_prefix(prefix: str) -> List[Dict[str, str]]:
|
||||||
|
vols = docker_volume_ls_json([])
|
||||||
|
keep = []
|
||||||
|
for v in vols:
|
||||||
|
name = v.get("Name")
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
if name.startswith(prefix + "_") or name.startswith(prefix + "-") or name == prefix:
|
||||||
|
keep.append(v)
|
||||||
|
return keep
|
||||||
|
|
||||||
|
def normalize_project_name(p: str) -> str:
|
||||||
|
return (p or "").replace(" ", "_")
|
||||||
|
|
||||||
|
# ---------- Compose config parsing ----------
|
||||||
|
|
||||||
|
def compose_config_json(files: List[pathlib.Path]) -> Optional[dict]:
|
||||||
|
if not files or not all(p.exists() for p in files):
|
||||||
|
return None
|
||||||
|
cmd = ["docker", "compose"]
|
||||||
|
for f in files:
|
||||||
|
cmd += ["-f", str(f)]
|
||||||
|
cmd += ["config", "--format", "json"]
|
||||||
|
try:
|
||||||
|
cp = run(cmd)
|
||||||
|
return json.loads(cp.stdout or "{}")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_short_volume_names_from_config(cfg: dict) -> Set[str]:
|
||||||
|
"""
|
||||||
|
Extrae short names de volúmenes usados en services[].volumes (type: volume)
|
||||||
|
y las claves del toplevel 'volumes'.
|
||||||
|
"""
|
||||||
|
names: Set[str] = set()
|
||||||
|
if not cfg:
|
||||||
|
return names
|
||||||
|
|
||||||
|
# services[].volumes
|
||||||
|
services = cfg.get("services") or {}
|
||||||
|
for svc in services.values():
|
||||||
|
vols = svc.get("volumes") or []
|
||||||
|
for m in vols:
|
||||||
|
# en JSON canonical, cada mount es un dict con 'type', 'source', 'target', ...
|
||||||
|
if isinstance(m, dict) and m.get("type") == "volume":
|
||||||
|
src = m.get("source")
|
||||||
|
if isinstance(src, str) and src:
|
||||||
|
names.add(src)
|
||||||
|
|
||||||
|
# top-level volumes (claves)
|
||||||
|
top_vols = cfg.get("volumes") or {}
|
||||||
|
if isinstance(top_vols, dict):
|
||||||
|
for k in top_vols.keys():
|
||||||
|
if isinstance(k, str) and k:
|
||||||
|
names.add(k)
|
||||||
|
|
||||||
|
return names
|
||||||
|
|
||||||
|
def docker_compose_name_from(files: List[pathlib.Path]) -> Optional[str]:
|
||||||
|
cfg = compose_config_json(files)
|
||||||
|
if cfg and isinstance(cfg, dict):
|
||||||
|
name = cfg.get("name")
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
def read_compose_project_from_env(env_path: pathlib.Path) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
if env_path.exists():
|
||||||
|
for line in env_path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith("COMPOSE_PROJECT_NAME="):
|
||||||
|
return line.split("=", 1)[1].strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def base_folder_slug() -> str:
|
||||||
|
return PROJECT_ROOT.name.lower().replace(" ", "_")
|
||||||
|
|
||||||
|
def candidates_for_env(env: str) -> List[str]:
|
||||||
|
cand: List[str] = []
|
||||||
|
if env == "development":
|
||||||
|
n1 = docker_compose_name_from([COMPOSE_BASE, COMPOSE_DEV])
|
||||||
|
n2 = read_compose_project_from_env(PROJECT_ROOT / ".env.development")
|
||||||
|
n3 = f"{base_folder_slug()}_dev"
|
||||||
|
n4 = f"{base_folder_slug()}-dev"
|
||||||
|
cand.extend([n1, n2, n3, n4, base_folder_slug()])
|
||||||
|
elif env == "production":
|
||||||
|
n1 = docker_compose_name_from([COMPOSE_BASE, COMPOSE_PROD])
|
||||||
|
n2 = read_compose_project_from_env(PROJECT_ROOT / ".env.production")
|
||||||
|
n3 = f"{base_folder_slug()}_prod"
|
||||||
|
n4 = f"{base_folder_slug()}-prod"
|
||||||
|
cand.extend([n1, n2, n3, n4, base_folder_slug()])
|
||||||
|
# dedup preservando orden
|
||||||
|
seen = set(); ordered = []
|
||||||
|
for x in cand:
|
||||||
|
if x and x not in seen:
|
||||||
|
seen.add(x); ordered.append(x)
|
||||||
|
return ordered
|
||||||
|
|
||||||
|
def candidates_for_global() -> List[str]:
|
||||||
|
cand: List[str] = []
|
||||||
|
# nombres desde compose globales
|
||||||
|
if COMPOSE_NPM.exists():
|
||||||
|
n = docker_compose_name_from([COMPOSE_NPM])
|
||||||
|
if n: cand.append(n)
|
||||||
|
if COMPOSE_DBVR.exists():
|
||||||
|
n = docker_compose_name_from([COMPOSE_DBVR])
|
||||||
|
if n and n not in cand: cand.append(n)
|
||||||
|
# fallback esperados
|
||||||
|
if GLOBAL_DEFAULT_PROJECT not in cand: cand.append(GLOBAL_DEFAULT_PROJECT)
|
||||||
|
bf = base_folder_slug()
|
||||||
|
if bf not in cand: cand.append(bf)
|
||||||
|
return cand
|
||||||
|
|
||||||
|
# ---------- Nueva detección por grupo: COMPOSE + labels ----------
|
||||||
|
|
||||||
|
def detect_group_volumes_with_compose(filesets: List[List[pathlib.Path]],
|
||||||
|
project_candidates: List[str]) -> Tuple[Optional[str], str, List[str]]:
|
||||||
|
"""
|
||||||
|
filesets: lista de listas de archivos compose a considerar (dev=[base,dev], prod=[base,prod],
|
||||||
|
global=[[npm], [dbeaver]] -> dos sets para unir shortnames).
|
||||||
|
Devuelve (project_seleccionado, metodo, [nombres_de_volumen]).
|
||||||
|
"""
|
||||||
|
# 1) Unir shortnames de todos los filesets
|
||||||
|
shortnames: Set[str] = set()
|
||||||
|
for files in filesets:
|
||||||
|
cfg = compose_config_json(files)
|
||||||
|
shortnames |= extract_short_volume_names_from_config(cfg)
|
||||||
|
|
||||||
|
# 2) Si hay shortnames, probar a buscar por (project,label.volume)
|
||||||
|
if shortnames:
|
||||||
|
for proj in project_candidates:
|
||||||
|
# Buscar cada shortname con ambos labels
|
||||||
|
found: List[str] = []
|
||||||
|
for sn in sorted(shortnames):
|
||||||
|
names = docker_volume_ls_names([
|
||||||
|
f"label=com.docker.compose.project={proj}",
|
||||||
|
f"label=com.docker.compose.volume={sn}"
|
||||||
|
])
|
||||||
|
if names:
|
||||||
|
found.extend(names)
|
||||||
|
# dedup preservando orden
|
||||||
|
if found:
|
||||||
|
seen = set(); ordered = []
|
||||||
|
for n in found:
|
||||||
|
if n not in seen:
|
||||||
|
seen.add(n); ordered.append(n)
|
||||||
|
return proj, f"compose+labels:{proj}", ordered
|
||||||
|
|
||||||
|
# 3) Fallback: probar cualquier volumen del proyecto (label) o por prefijo
|
||||||
|
for proj in project_candidates:
|
||||||
|
method, rows = discover_volumes_for_project(proj)
|
||||||
|
if rows:
|
||||||
|
return proj, f"fallback:{method}", [r.get("Name") for r in rows if r.get("Name")]
|
||||||
|
|
||||||
|
# 4) Nada
|
||||||
|
first = project_candidates[0] if project_candidates else None
|
||||||
|
return first, "none", []
|
||||||
|
|
||||||
|
def discover_volumes_for_project(project_raw: str) -> Tuple[str, List[Dict[str, str]]]:
|
||||||
|
"""
|
||||||
|
Método previo de respaldo: por label de proyecto y prefijo (para CLI y fallback).
|
||||||
|
"""
|
||||||
|
project_norm = normalize_project_name(project_raw)
|
||||||
|
project_lower = project_norm.lower()
|
||||||
|
|
||||||
|
vols = list_by_label_project(project_norm)
|
||||||
|
if vols:
|
||||||
|
return f"label:{project_norm}", vols
|
||||||
|
|
||||||
|
vols2 = list_by_label_project(project_lower)
|
||||||
|
if vols2:
|
||||||
|
return f"label:{project_lower}", vols2
|
||||||
|
|
||||||
|
by_name = list_by_name_prefix(project_norm)
|
||||||
|
if by_name:
|
||||||
|
return f"name-prefix:{project_norm}", by_name
|
||||||
|
|
||||||
|
by_name2 = list_by_name_prefix(project_lower)
|
||||||
|
if by_name2:
|
||||||
|
return f"name-prefix:{project_lower}", by_name2
|
||||||
|
|
||||||
|
return "none", []
|
||||||
|
|
||||||
|
# ---------- Backup helpers ----------
|
||||||
|
|
||||||
|
def ensure_alpine_image():
|
||||||
|
try:
|
||||||
|
run(["docker", "image", "inspect", "alpine:latest"])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Pulling alpine:latest ...")
|
||||||
|
run(["docker", "pull", "alpine:latest"], check=True, capture_output=False)
|
||||||
|
|
||||||
|
def build_archive_name(project: str, volume_name: str, ts: str) -> str:
|
||||||
|
"""
|
||||||
|
Construye el nombre del .tar.gz evitando duplicar el prefijo del proyecto.
|
||||||
|
- Si volume_name ya empieza con '<project>_' o '<project>-', se usa tal cual.
|
||||||
|
- Si no, se antepone '<project>_'.
|
||||||
|
Resultado: <project>_<shortname>-<ts>.tar.gz
|
||||||
|
"""
|
||||||
|
proj_token = project.lower().replace(" ", "_")
|
||||||
|
v_lower = volume_name.lower()
|
||||||
|
if v_lower.startswith(proj_token + "_") or v_lower.startswith(proj_token + "-"):
|
||||||
|
base = volume_name
|
||||||
|
else:
|
||||||
|
base = f"{proj_token}_{volume_name}"
|
||||||
|
return f"{base}-{ts}.tar.gz"
|
||||||
|
|
||||||
|
def backup_volume(volume_name: str, out_dir: pathlib.Path, archive_name: str, dry_run: bool = False) -> int:
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
out_dir_abs = out_dir.resolve()
|
||||||
|
out_path = out_dir_abs / archive_name
|
||||||
|
docker_cmd = [
|
||||||
|
"docker", "run", "--rm",
|
||||||
|
"-v", f"{volume_name}:/volume:ro",
|
||||||
|
"-v", f"{str(out_dir_abs)}:/backup",
|
||||||
|
# "--user", f"{os.getuid()}:{os.getgid()}",
|
||||||
|
"alpine:latest",
|
||||||
|
"sh", "-lc",
|
||||||
|
f"tar czf /backup/{shlex.quote(out_path.name)} -C /volume ."
|
||||||
|
]
|
||||||
|
if dry_run:
|
||||||
|
print("[DRY RUN] Would run:", " ".join(shlex.quote(c) for c in docker_cmd))
|
||||||
|
return 0
|
||||||
|
cp = subprocess.run(docker_cmd)
|
||||||
|
return cp.returncode
|
||||||
|
|
||||||
|
def backup_explicit(volume_names: List[str], ts: str, output_dir: Optional[str], dry_run: bool, prefix_project: Optional[str]) -> int:
|
||||||
|
"""
|
||||||
|
Respalda exactamente los volúmenes indicados.
|
||||||
|
- Directorio por defecto: ./docker-volumes-<ts>
|
||||||
|
- Nombre de archivo: build_archive_name(prefix_project, volume_name, ts)
|
||||||
|
"""
|
||||||
|
out_dir = pathlib.Path(output_dir) if output_dir else (PROJECT_ROOT / f"docker-volumes-{ts}")
|
||||||
|
if not dry_run:
|
||||||
|
ensure_alpine_image()
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
for vname in volume_names:
|
||||||
|
if not vname:
|
||||||
|
continue
|
||||||
|
archive = build_archive_name(prefix_project or "", vname, ts)
|
||||||
|
print(f"Backing up volume: {vname} -> {archive}")
|
||||||
|
rc = backup_volume(vname, out_dir, archive, dry_run=dry_run)
|
||||||
|
if rc != 0:
|
||||||
|
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
|
||||||
|
failures.append(vname)
|
||||||
|
if failures:
|
||||||
|
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
print("\nAll done. Archives written to:", str(out_dir.resolve()))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def backup_group(project_name: str, ts: str, output_dir: Optional[str] = None,
|
||||||
|
dry_run: bool = False, excludes: Optional[List[str]] = None) -> int:
|
||||||
|
"""
|
||||||
|
Fallback legacy (label/prefix). Mantiene coherencia con nombres y directorio por defecto.
|
||||||
|
"""
|
||||||
|
method, rows = discover_volumes_for_project(project_name)
|
||||||
|
|
||||||
|
print_header(f"Proyecto '{project_name}': {len(rows)} volumen(es) detectado(s) (método: {method})")
|
||||||
|
for v in rows:
|
||||||
|
print(" -", v.get("Name"))
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
warn("No hay volúmenes para respaldar.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vols = [v.get("Name") for v in rows if v.get("Name")]
|
||||||
|
if excludes:
|
||||||
|
excl = set(excludes)
|
||||||
|
vols = [n for n in vols if n not in excl]
|
||||||
|
if not vols:
|
||||||
|
warn("Tras aplicar exclusiones, no quedó nada por respaldar.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
out_dir = pathlib.Path(output_dir) if output_dir else (PROJECT_ROOT / f"docker-volumes-{ts}")
|
||||||
|
if not dry_run:
|
||||||
|
ensure_alpine_image()
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
for vname in vols:
|
||||||
|
archive = build_archive_name(project_name, vname, ts)
|
||||||
|
print(f"Backing up volume: {vname} -> {archive}")
|
||||||
|
rc = backup_volume(vname, out_dir, archive, dry_run=dry_run)
|
||||||
|
if rc != 0:
|
||||||
|
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
|
||||||
|
failures.append(vname)
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
print("\nAll done. Archives written to:", str(out_dir.resolve()))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# ---------- UI helpers ----------
|
||||||
|
|
||||||
|
def yes_no(prompt: str, default="n") -> bool:
|
||||||
|
default = default.lower()
|
||||||
|
hint = "[Y/n]" if default == "y" else "[y/N]"
|
||||||
|
while True:
|
||||||
|
resp = input(f"{prompt} {hint} ").strip().lower()
|
||||||
|
if not resp:
|
||||||
|
return default == "y"
|
||||||
|
if resp in ("y","yes","s","si","sí"):
|
||||||
|
return True
|
||||||
|
if resp in ("n","no"):
|
||||||
|
return False
|
||||||
|
print("Respuesta no reconocida. Por favor, responde con 'y' o 'n'.")
|
||||||
|
|
||||||
|
def print_header(title: str):
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(title)
|
||||||
|
print("=" * 60 + "\n")
|
||||||
|
|
||||||
|
def info(msg): print(f"• {msg}")
|
||||||
|
def ok(msg): print(f"✓ {msg}")
|
||||||
|
def warn(msg): print(f"! {msg}")
|
||||||
|
def fail(msg):
|
||||||
|
print(f"✗ {msg}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ---------- Menú interactivo ----------
|
||||||
|
|
||||||
|
def interactive_menu():
|
||||||
|
if not which("docker"):
|
||||||
|
fail("ERROR: 'docker' no está en el PATH.")
|
||||||
|
try:
|
||||||
|
run(["docker", "version"], check=True, capture_output=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
fail("ERROR: No se puede hablar con el daemon de Docker. ¿Está corriendo? ¿Tu usuario está en el grupo 'docker'?")
|
||||||
|
|
||||||
|
# DEV
|
||||||
|
dev_candidates = candidates_for_env("development") if COMPOSE_BASE.exists() and COMPOSE_DEV.exists() else []
|
||||||
|
dev_proj, dev_method, dev_names = detect_group_volumes_with_compose(
|
||||||
|
filesets=[[COMPOSE_BASE, COMPOSE_DEV]] if dev_candidates else [],
|
||||||
|
project_candidates=dev_candidates
|
||||||
|
)
|
||||||
|
|
||||||
|
# PROD
|
||||||
|
prod_candidates = candidates_for_env("production") if COMPOSE_BASE.exists() and COMPOSE_PROD.exists() else []
|
||||||
|
prod_proj, prod_method, prod_names = detect_group_volumes_with_compose(
|
||||||
|
filesets=[[COMPOSE_BASE, COMPOSE_PROD]] if prod_candidates else [],
|
||||||
|
project_candidates=prod_candidates
|
||||||
|
)
|
||||||
|
|
||||||
|
# GLOBAL = NPM + DBEAVER (unir shortnames de ambos)
|
||||||
|
global_candidates = candidates_for_global()
|
||||||
|
global_filesets = []
|
||||||
|
if COMPOSE_NPM.exists():
|
||||||
|
global_filesets.append([COMPOSE_NPM])
|
||||||
|
if COMPOSE_DBVR.exists():
|
||||||
|
global_filesets.append([COMPOSE_DBVR])
|
||||||
|
glob_proj, glob_method, glob_names = detect_group_volumes_with_compose(
|
||||||
|
filesets=global_filesets,
|
||||||
|
project_candidates=global_candidates
|
||||||
|
)
|
||||||
|
|
||||||
|
# Resumen
|
||||||
|
print_header("Resumen de volúmenes detectados")
|
||||||
|
if dev_proj:
|
||||||
|
info(f"DESARROLLO ({dev_proj}): {len(dev_names)} volumen(es) (método: {dev_method})")
|
||||||
|
else:
|
||||||
|
info("DESARROLLO: archivos compose no encontrados.")
|
||||||
|
if prod_proj:
|
||||||
|
info(f"PRODUCCIÓN ({prod_proj}): {len(prod_names)} volumen(es) (método: {prod_method})")
|
||||||
|
else:
|
||||||
|
info("PRODUCCIÓN: archivos compose no encontrados.")
|
||||||
|
if glob_proj:
|
||||||
|
info(f"GLOBALES ({glob_proj}): {len(glob_names)} volumen(es) (método: {glob_method})")
|
||||||
|
else:
|
||||||
|
info("GLOBALES: no se detectaron archivos compose globales.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Menú
|
||||||
|
options = {}
|
||||||
|
key = 1
|
||||||
|
if dev_proj:
|
||||||
|
print(f" {key}) Respaldar volúmenes de DESARROLLO ({dev_proj})")
|
||||||
|
options[str(key)] = ("backup_explicit", dev_proj, dev_names); key += 1
|
||||||
|
if prod_proj:
|
||||||
|
print(f" {key}) Respaldar volúmenes de PRODUCCIÓN ({prod_proj})")
|
||||||
|
options[str(key)] = ("backup_explicit", prod_proj, prod_names); key += 1
|
||||||
|
if glob_proj:
|
||||||
|
print(f" {key}) Respaldar volúmenes GLOBALES ({glob_proj})")
|
||||||
|
options[str(key)] = ("backup_explicit", glob_proj, glob_names); key += 1
|
||||||
|
|
||||||
|
# TODOS: unión deduplicada por nombre (respalda 1 vez cada volumen)
|
||||||
|
groups = []
|
||||||
|
if dev_proj: groups.append( (dev_proj, dev_names) )
|
||||||
|
if prod_proj: groups.append( (prod_proj, prod_names) )
|
||||||
|
if glob_proj: groups.append( (glob_proj, glob_names) )
|
||||||
|
|
||||||
|
if len(groups) >= 2:
|
||||||
|
print(f" {key}) Respaldar TODOS los grupos detectados")
|
||||||
|
options[str(key)] = ("backup_all_explicit", groups); key += 1
|
||||||
|
|
||||||
|
print(f" {key}) Salir")
|
||||||
|
exit_key = str(key)
|
||||||
|
|
||||||
|
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input("> ").strip()
|
||||||
|
if choice == exit_key:
|
||||||
|
ok("Saliendo.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if choice not in options:
|
||||||
|
print("Opción inválida.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
action = options[choice][0]
|
||||||
|
dry = yes_no("¿Dry-run (no escribir archivos)?", default="n")
|
||||||
|
outd = input(f"Directorio de salida (vacío = ./docker-volumes-{ts}): ").strip() or None
|
||||||
|
excl_input = input("Excluir volúmenes (nombres separados por coma, vacío = ninguno): ").strip()
|
||||||
|
excludes = set(e.strip() for e in excl_input.split(",") if e.strip()) if excl_input else set()
|
||||||
|
|
||||||
|
if action == "backup_explicit":
|
||||||
|
_, proj, names = options[choice]
|
||||||
|
names = [n for n in names if n not in excludes]
|
||||||
|
if not names:
|
||||||
|
warn("No hay volúmenes para respaldar.")
|
||||||
|
sys.exit(0)
|
||||||
|
rc = backup_explicit(names, ts, output_dir=outd, dry_run=dry, prefix_project=proj)
|
||||||
|
sys.exit(rc)
|
||||||
|
|
||||||
|
elif action == "backup_all_explicit":
|
||||||
|
_, groups_payload = options[choice]
|
||||||
|
vol_to_proj: Dict[str, str] = {}
|
||||||
|
for proj, names in groups_payload:
|
||||||
|
for n in names:
|
||||||
|
if n not in excludes and n not in vol_to_proj:
|
||||||
|
vol_to_proj[n] = proj
|
||||||
|
if not vol_to_proj:
|
||||||
|
warn("No hay volúmenes para respaldar.")
|
||||||
|
sys.exit(0)
|
||||||
|
if not dry:
|
||||||
|
ensure_alpine_image()
|
||||||
|
out_dir = pathlib.Path(outd) if outd else (PROJECT_ROOT / f"docker-volumes-{ts}")
|
||||||
|
failures = []
|
||||||
|
for vname, proj in vol_to_proj.items():
|
||||||
|
archive = build_archive_name(proj, vname, ts)
|
||||||
|
print(f"Backing up volume: {vname} -> {archive}")
|
||||||
|
rc = backup_volume(vname, out_dir, archive, dry_run=dry)
|
||||||
|
if rc != 0:
|
||||||
|
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
|
||||||
|
failures.append(vname)
|
||||||
|
if failures:
|
||||||
|
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("\nAll done. Archives written to:", str(out_dir.resolve()))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ---------- CLI legacy (se mantiene) ----------
|
||||||
|
|
||||||
|
def detect_project_name(args_project: Optional[str]) -> str:
|
||||||
|
if args_project:
|
||||||
|
return args_project
|
||||||
|
env_name = os.environ.get("COMPOSE_PROJECT_NAME")
|
||||||
|
if env_name:
|
||||||
|
return env_name
|
||||||
|
return PROJECT_ROOT.name.replace(" ", "_")
|
||||||
|
|
||||||
|
def cli_main():
|
||||||
|
parser = argparse.ArgumentParser(description="Export (compress) every Docker volume of a Docker Compose project.")
|
||||||
|
parser.add_argument("-p", "--project", help="Compose project or prefix (see --discovery).")
|
||||||
|
parser.add_argument("-o", "--output", help="Output directory (default: ./docker-volumes-<timestamp>).")
|
||||||
|
parser.add_argument("--exclude", nargs="*", default=[], help="Volume names to exclude (space-separated).")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without doing it.")
|
||||||
|
parser.add_argument("--timestamp", default=datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
|
||||||
|
help="Timestamp to embed into filenames (default: current time).")
|
||||||
|
parser.add_argument("--discovery", choices=["auto","label","name"], default="auto",
|
||||||
|
help="How to discover volumes: 'label' (strict), 'name' (prefix), or 'auto' (default).")
|
||||||
|
parser.add_argument("--list-only", action="store_true", help="Only list volumes that would be backed up and exit.")
|
||||||
|
parser.add_argument("--menu", action="store_true", help="Launch interactive menu instead of CLI behavior.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.menu or not args.project:
|
||||||
|
interactive_menu()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not which("docker"):
|
||||||
|
print("ERROR: 'docker' not on PATH.", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
project_raw = detect_project_name(args.project)
|
||||||
|
project_norm = normalize_project_name(project_raw)
|
||||||
|
project_lower = project_norm.lower()
|
||||||
|
ts = args.timestamp
|
||||||
|
out_dir = pathlib.Path(args.output) if args.output else (PROJECT_ROOT / f"docker-volumes-{ts}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(["docker", "version"], check=True, capture_output=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("ERROR: Docker daemon not reachable.", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# Descubrimiento legacy por label/prefijo (se mantiene para compatibilidad)
|
||||||
|
selected = []
|
||||||
|
method_used = None
|
||||||
|
vols = list_by_label_project(project_norm)
|
||||||
|
if vols:
|
||||||
|
selected = vols; method_used = f"label:{project_norm}"
|
||||||
|
else:
|
||||||
|
vols2 = list_by_label_project(project_lower)
|
||||||
|
if vols2:
|
||||||
|
selected = vols2; method_used = f"label:{project_lower}"
|
||||||
|
if not selected:
|
||||||
|
by_name = list_by_name_prefix(project_norm)
|
||||||
|
if by_name:
|
||||||
|
selected = by_name; method_used = f"name-prefix:{project_norm}"
|
||||||
|
else:
|
||||||
|
by_name2 = list_by_name_prefix(project_lower)
|
||||||
|
if by_name2:
|
||||||
|
selected = by_name2; method_used = f"name-prefix:{project_lower}"
|
||||||
|
|
||||||
|
if not selected:
|
||||||
|
print(f"No volumes found for project/prefix '{project_raw}'.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
exclude_set = set(args.exclude or [])
|
||||||
|
names = [v.get("Name") for v in selected if v.get("Name") not in exclude_set]
|
||||||
|
|
||||||
|
print(f"Discovery method: {method_used}")
|
||||||
|
print(f"Volumes discovered: {len(names)}")
|
||||||
|
for n in names:
|
||||||
|
print(" -", n)
|
||||||
|
|
||||||
|
if args.list_only:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.dry_run:
|
||||||
|
ensure_alpine_image()
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
for vname in names:
|
||||||
|
archive = build_archive_name(project_lower, vname, ts)
|
||||||
|
print(f"Backing up volume: {vname} -> {archive}")
|
||||||
|
rc = backup_volume(vname, out_dir, archive, dry_run=args.dry_run)
|
||||||
|
if rc != 0:
|
||||||
|
print(f" ERROR: backup failed for volume '{vname}' (exit code {rc})", file=sys.stderr)
|
||||||
|
failures.append(vname)
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print("\nCompleted with errors. Failed volumes:", ", ".join(failures))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("\nAll done. Archives written to:", str(out_dir.resolve()))
|
||||||
|
|
||||||
|
# ---------- Entry point ----------
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
interactive_menu()
|
||||||
|
else:
|
||||||
|
cli_main()
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# docker-compose.overrride.yml
|
||||||
|
# Docker Comose para entorno de desarrollo o development.
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: node:20-bookworm
|
||||||
|
expose:
|
||||||
|
- ${APP_LOCAL_PORT}
|
||||||
|
working_dir: /app
|
||||||
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
|
volumes:
|
||||||
|
- ./services/app:/app:rw
|
||||||
|
- ./services/app/node_modules:/app/node_modules
|
||||||
|
env_file:
|
||||||
|
- ./services/app/.env.development
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [dev-app]
|
||||||
|
command: npm run dev
|
||||||
|
|
||||||
|
auth:
|
||||||
|
image: node:20-bookworm
|
||||||
|
expose:
|
||||||
|
- ${AUTH_LOCAL_PORT}
|
||||||
|
working_dir: /app
|
||||||
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
|
volumes:
|
||||||
|
- ./services/auth:/app:rw
|
||||||
|
- ./services/auth/node_modules:/app/node_modules
|
||||||
|
env_file:
|
||||||
|
- ./services/auth/.env.development
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
command: npm run dev
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [dev-auth]
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASS}
|
||||||
|
volumes:
|
||||||
|
- suitecoffee-db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [dev-db]
|
||||||
|
|
||||||
|
tenants:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${TENANTS_DB_NAME}
|
||||||
|
POSTGRES_USER: ${TENANTS_DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
||||||
|
volumes:
|
||||||
|
- tenants-db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [dev-tenants]
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
tenants-db:
|
||||||
|
suitecoffee-db:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# compose.prod.yml
|
||||||
|
# Docker Comose para entorno de producción o production.
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ./services/app
|
||||||
|
dockerfile: Dockerfile.production
|
||||||
|
expose:
|
||||||
|
- ${APP_LOCAL_PORT}
|
||||||
|
volumes:
|
||||||
|
- ./services/app:/app
|
||||||
|
env_file:
|
||||||
|
- ./services/app/.env.production
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [prod-app]
|
||||||
|
command: npm run start
|
||||||
|
|
||||||
|
auth:
|
||||||
|
build:
|
||||||
|
context: ./services/auth
|
||||||
|
dockerfile: Dockerfile.production
|
||||||
|
expose:
|
||||||
|
- ${AUTH_LOCAL_PORT}
|
||||||
|
volumes:
|
||||||
|
- ./services/auth:/app
|
||||||
|
env_file:
|
||||||
|
- ./services/auth/.env.production
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
command: npm run start
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [prod-auth]
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASS}
|
||||||
|
volumes:
|
||||||
|
- suitecoffee-db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [prod-db]
|
||||||
|
|
||||||
|
tenants:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${TENANTS_DB_NAME}
|
||||||
|
POSTGRES_USER: ${TENANTS_DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
||||||
|
volumes:
|
||||||
|
- tenants-db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
aliases: [prod-tenants]
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
tenants-db:
|
||||||
|
suitecoffee-db:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
net:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# compose.tools.yaml
|
||||||
|
name: suitecoffee_tools
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
dbeaver:
|
||||||
|
image: dbeaver/cloudbeaver:latest
|
||||||
|
profiles: [dbeaver]
|
||||||
|
ports:
|
||||||
|
- 8978:8978
|
||||||
|
environment:
|
||||||
|
TZ: America/Montevideo
|
||||||
|
volumes:
|
||||||
|
- dbeaver_logs:/opt/cloudbeaver/logs
|
||||||
|
- dbeaver_workspace:/opt/cloudbeaver/workspace
|
||||||
|
networks:
|
||||||
|
suitecoffee_prod_net: {}
|
||||||
|
suitecoffee_dev_net: {}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://localhost:8978 || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
start_period: 20s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
npm:
|
||||||
|
image: jc21/nginx-proxy-manager:latest
|
||||||
|
profiles: [npm]
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "80:80" # HTTP público
|
||||||
|
- "81:81" # UI de administración
|
||||||
|
environment:
|
||||||
|
TZ: America/Montevideo
|
||||||
|
volumes:
|
||||||
|
- npm_data:/data
|
||||||
|
- npm_letsencrypt:/etc/letsencrypt
|
||||||
|
networks:
|
||||||
|
suitecoffee_prod_net: {}
|
||||||
|
suitecoffee_dev_net: {}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://localhost:81 || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
suitecoffee_dev_net:
|
||||||
|
external: true
|
||||||
|
suitecoffee_prod_net:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
npm_data:
|
||||||
|
npm_letsencrypt:
|
||||||
|
|
||||||
|
dbeaver_logs:
|
||||||
|
dbeaver_workspace:
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# compose.yml
|
||||||
|
# Comose base
|
||||||
|
name: ${COMPOSE_PROJECT_NAME:-suitecoffee}
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# app:
|
||||||
|
# depends_on:
|
||||||
|
# db:
|
||||||
|
# condition: service_healthy
|
||||||
|
# tenants:
|
||||||
|
# condition: service_healthy
|
||||||
|
# healthcheck:
|
||||||
|
# test: ["CMD-SHELL", "curl -fsS http://localhost:${APP_DOCKER_PORT}/health || exit 1"]
|
||||||
|
# interval: 10s
|
||||||
|
# timeout: 3s
|
||||||
|
# retries: 10
|
||||||
|
# start_period: 20s
|
||||||
|
# restart: unless-stopped
|
||||||
|
|
||||||
|
# auth:
|
||||||
|
# depends_on:
|
||||||
|
# db:
|
||||||
|
# condition: service_healthy
|
||||||
|
# healthcheck:
|
||||||
|
# test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
|
||||||
|
# interval: 10s
|
||||||
|
# timeout: 3s
|
||||||
|
# retries: 10
|
||||||
|
# start_period: 15s
|
||||||
|
# restart: unless-stopped
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
TZ: America/Montevideo
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
tenants:
|
||||||
|
image: postgres:16
|
||||||
|
environment:
|
||||||
|
TZ: America/Montevideo
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${TENANTS_DB_USER} -d ${TENANTS_DB_NAME}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
# docker-compose.overrride.yml
|
|
||||||
# Docker Comose para entorno de desarrollo o development.
|
|
||||||
|
|
||||||
services:
|
|
||||||
npm:
|
|
||||||
image: jc21/nginx-proxy-manager:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
app:
|
|
||||||
condition: service_healthy
|
|
||||||
auth:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "80:80" # HTTP público
|
|
||||||
- "81:81" # UI de administración NPM
|
|
||||||
- "443:443" # HTTPS público
|
|
||||||
volumes:
|
|
||||||
- npm_data:/data # config + DB (SQLite)
|
|
||||||
- npm_letsencrypt:/etc/letsencrypt
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app:
|
|
||||||
image: node:20-bookworm
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
tenants:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- 3000:3000
|
|
||||||
working_dir: /app
|
|
||||||
user: "${UID:-1000}:${GID:-1000}"
|
|
||||||
volumes:
|
|
||||||
- ./services/app:/app:rw
|
|
||||||
- ./node_modules:/app/node_modules
|
|
||||||
env_file:
|
|
||||||
- ./services/app/.env.development
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=${NODE_ENV}
|
|
||||||
command: npm run dev
|
|
||||||
healthcheck:
|
|
||||||
# IMPORTANTE: asegurate de tener curl instalado en la imagen de app (ver nota abajo)
|
|
||||||
test: ["CMD-SHELL", "curl -fsS http://localhost:${APP_DOCKER_PORT}/health || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 20s
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
auth:
|
|
||||||
image: node:20-bookworm
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- 4000:4000
|
|
||||||
working_dir: /app
|
|
||||||
user: "${UID:-1000}:${GID:-1000}"
|
|
||||||
volumes:
|
|
||||||
- ./services/app:/app:rw
|
|
||||||
- ./node_modules:/app/node_modules
|
|
||||||
env_file:
|
|
||||||
- ./services/auth/.env.development
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=${NODE_ENV}
|
|
||||||
command: npm run dev
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 15s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DB_NAME}
|
|
||||||
POSTGRES_USER: ${DB_USER}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASS}
|
|
||||||
ports:
|
|
||||||
- ${DB_LOCAL_PORT}:${DB_DOCKER_PORT}
|
|
||||||
volumes:
|
|
||||||
- suitecoffee-db:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 20
|
|
||||||
start_period: 10s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
tenants:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${TENANTS_DB_NAME}
|
|
||||||
POSTGRES_USER: ${TENANTS_DB_USER}
|
|
||||||
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
|
||||||
volumes:
|
|
||||||
- tenants-db:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- ${TENANTS_DB_LOCAL_PORT}:${TENANTS_DB_DOCKER_PORT}
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${TENANTS_DB_USER} -d ${TENANTS_DB_NAME}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 20
|
|
||||||
start_period: 10s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
dbeaver:
|
|
||||||
image: dbeaver/cloudbeaver:latest
|
|
||||||
# depends_on:
|
|
||||||
# tenants:
|
|
||||||
# condition: service_healthy
|
|
||||||
# db:
|
|
||||||
# condition: service_healthy
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 8978:8978
|
|
||||||
volumes:
|
|
||||||
- dbeaver_logs:/opt/cloudbeaver/logs
|
|
||||||
- dbeaver_workspace:/opt/cloudbeaver/workspace
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
tenants-db:
|
|
||||||
suitecoffee-db:
|
|
||||||
|
|
||||||
npm_data:
|
|
||||||
npm_letsencrypt:
|
|
||||||
dbeaver_logs:
|
|
||||||
dbeaver_workspace:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
suitecoffee-net:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
# docker-compose.yml
|
|
||||||
# Docker Comose para entorno de producción o production.
|
|
||||||
name: ${COMPOSE_PROJECT_NAME:-suitecoffee}
|
|
||||||
|
|
||||||
services:
|
|
||||||
npm:
|
|
||||||
image: jc21/nginx-proxy-manager:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
app:
|
|
||||||
condition: service_healthy
|
|
||||||
auth:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "80:80" # HTTP público
|
|
||||||
- "81:81" # UI de administración NPM
|
|
||||||
- "443:443" # HTTPS público
|
|
||||||
volumes:
|
|
||||||
- npm_data:/data # config + DB (SQLite)
|
|
||||||
- npm_letsencrypt:/etc/letsencrypt
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
app:
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
tenants:
|
|
||||||
condition: service_healthy
|
|
||||||
build:
|
|
||||||
context: ./services/app
|
|
||||||
dockerfile: Dockerfile.development
|
|
||||||
volumes:
|
|
||||||
- ./services/app:/app
|
|
||||||
env_file:
|
|
||||||
- ./services/app/.env.development
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=${NODE_ENV}
|
|
||||||
command: npm run start
|
|
||||||
healthcheck:
|
|
||||||
# IMPORTANTE: asegurate de tener curl instalado en la imagen de app (ver nota abajo)
|
|
||||||
test: ["CMD-SHELL", "curl -fsS http://localhost:${APP_DOCKER_PORT}/health || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 20s
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
auth:
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
build:
|
|
||||||
context: ./services/auth
|
|
||||||
dockerfile: Dockerfile.development
|
|
||||||
volumes:
|
|
||||||
- ./services/auth:/app
|
|
||||||
env_file:
|
|
||||||
- ./services/auth/.env.development
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=${NODE_ENV}
|
|
||||||
command: npm run start
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 15s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DB_NAME}
|
|
||||||
POSTGRES_USER: ${DB_USER}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASS}
|
|
||||||
volumes:
|
|
||||||
- suitecoffee-db:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 20
|
|
||||||
start_period: 10s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
tenants:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${TENANTS_DB_NAME}
|
|
||||||
POSTGRES_USER: ${TENANTS_DB_USER}
|
|
||||||
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
|
||||||
volumes:
|
|
||||||
- tenants-db:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${TENANTS_DB_USER} -d ${TENANTS_DB_NAME}"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 20
|
|
||||||
start_period: 10s
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
dbeaver:
|
|
||||||
image: dbeaver/cloudbeaver:latest
|
|
||||||
# depends_on:
|
|
||||||
# tenants:
|
|
||||||
# condition: service_healthy
|
|
||||||
# db:
|
|
||||||
# condition: service_healthy
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "8978:8978"
|
|
||||||
volumes:
|
|
||||||
- dbeaver_logs:/opt/cloudbeaver/logs
|
|
||||||
- dbeaver_workspace:/opt/cloudbeaver/workspace
|
|
||||||
networks:
|
|
||||||
- suitecoffee-net
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
tenants-db:
|
|
||||||
suitecoffee-db:
|
|
||||||
|
|
||||||
npm_data:
|
|
||||||
npm_letsencrypt:
|
|
||||||
dbeaver_logs:
|
|
||||||
dbeaver_workspace:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
suitecoffee-net:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@startuml Modelo Entidad-Relacion
|
|
||||||
class Producto {
|
|
||||||
- idProducto: int
|
|
||||||
+ getId(): int
|
|
||||||
}
|
|
||||||
@enduml
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
docker compose -f docker-compose.yml -f docker-compose.override.yml \
|
|
||||||
--env-file .env.development up -d
|
|
||||||
Generated
-1482
File diff suppressed because it is too large
Load Diff
+18
-3
@@ -1,6 +1,21 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"name": "suitecoffee",
|
||||||
"app": "file:services/app",
|
"version": "1.0.0",
|
||||||
"auth": "file:services/auth"
|
"description": "Software para gestión de cafeterías",
|
||||||
|
"keywords": [
|
||||||
|
"coffee",
|
||||||
|
"suite",
|
||||||
|
"suitecoffee"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea.mateosaldain.uy/msaldain/SuiteCoffee.git"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"author": "Mateo Saldain",
|
||||||
|
"type": "module",
|
||||||
|
"main": "suitecoffee.py",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,466 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
restore_compose_volumes.py
|
||||||
|
--------------------------
|
||||||
|
Restaura volúmenes desde backups generados por backup_compose_volumes.py.
|
||||||
|
|
||||||
|
- Busca carpetas ./docker-volumes-<timestamp>
|
||||||
|
- Lee .tar.gz (nombres: <volume_name>-<YYYYMMDD-HHMMSS>.tar.gz)
|
||||||
|
- Dos modos:
|
||||||
|
1) Tradicional (sin labels)
|
||||||
|
2) Reconocido por Compose (aplica labels com.docker.compose.* para evitar el warning)
|
||||||
|
|
||||||
|
Además:
|
||||||
|
- Si un volumen existe y está en uso, ofrece detener y eliminar contenedores que lo usan
|
||||||
|
para poder recrearlo con labels correctos (solo en modo 2).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shlex
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
from typing import List, Tuple, Dict, Optional
|
||||||
|
|
||||||
|
PROJECT_ROOT = pathlib.Path.cwd()
|
||||||
|
BACKUP_DIR_PATTERN = re.compile(r"^docker-volumes-\d{8}-\d{6}$")
|
||||||
|
ARCHIVE_PATTERN = re.compile(r"^(?P<basename>.+)-(?P<ts>\d{8}-\d{6})\.tar\.gz$")
|
||||||
|
|
||||||
|
# ---------- utils ----------
|
||||||
|
|
||||||
|
def run(cmd: List[str], check: bool = False, capture_output: bool = True, text: bool = True) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run(cmd, check=check, capture_output=capture_output, text=text)
|
||||||
|
|
||||||
|
def which(prog: str) -> bool:
|
||||||
|
from shutil import which as _w
|
||||||
|
return _w(prog) is not None
|
||||||
|
|
||||||
|
def fail(msg: str):
|
||||||
|
print(f"✗ {msg}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def ok(msg: str):
|
||||||
|
print(f"✓ {msg}")
|
||||||
|
|
||||||
|
def info(msg: str):
|
||||||
|
print(f"• {msg}")
|
||||||
|
|
||||||
|
def warn(msg: str):
|
||||||
|
print(f"! {msg}")
|
||||||
|
|
||||||
|
def yes_no(prompt: str, default: str = "n") -> bool:
|
||||||
|
default = default.lower()
|
||||||
|
hint = "[Y/n]" if default == "y" else "[y/N]"
|
||||||
|
while True:
|
||||||
|
resp = input(f"{prompt} {hint} ").strip().lower()
|
||||||
|
if not resp:
|
||||||
|
return default == "y"
|
||||||
|
if resp in ("y","yes","s","si","sí"):
|
||||||
|
return True
|
||||||
|
if resp in ("n","no"):
|
||||||
|
return False
|
||||||
|
print("Respuesta no reconocida. Responde 'y' o 'n'.")
|
||||||
|
|
||||||
|
# ---------- docker helpers ----------
|
||||||
|
|
||||||
|
def ensure_alpine_image():
|
||||||
|
try:
|
||||||
|
run(["docker", "image", "inspect", "alpine:latest"], check=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
info("Descargando alpine:latest ...")
|
||||||
|
run(["docker", "pull", "alpine:latest"], check=True, capture_output=False, text=True)
|
||||||
|
|
||||||
|
def volume_exists(name: str) -> bool:
|
||||||
|
try:
|
||||||
|
run(["docker", "volume", "inspect", name], check=True)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def inspect_volume_labels(name: str) -> Dict[str, str]:
|
||||||
|
try:
|
||||||
|
cp = run(["docker", "volume", "inspect", name, "--format", "{{json .Labels}}"], check=True)
|
||||||
|
return json.loads(cp.stdout or "null") or {}
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def containers_using_volume(name: str) -> List[str]:
|
||||||
|
# docker ps soporta --filter volume=<name>
|
||||||
|
try:
|
||||||
|
cp = run(["docker", "ps", "-a", "--filter", f"volume={name}", "-q"], check=True)
|
||||||
|
return [l.strip() for l in cp.stdout.splitlines() if l.strip()]
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def stop_containers(ids: List[str]) -> None:
|
||||||
|
if not ids:
|
||||||
|
return
|
||||||
|
info("Deteniendo contenedores que usan el volumen...")
|
||||||
|
run(["docker", "stop"] + ids, check=False, capture_output=False)
|
||||||
|
|
||||||
|
def remove_containers(ids: List[str]) -> None:
|
||||||
|
if not ids:
|
||||||
|
return
|
||||||
|
info("Eliminando contenedores detenidos que usan el volumen...")
|
||||||
|
run(["docker", "rm"] + ids, check=False, capture_output=False)
|
||||||
|
|
||||||
|
def remove_volume(name: str) -> bool:
|
||||||
|
try:
|
||||||
|
run(["docker", "volume", "rm", "-f", name], check=True, capture_output=False)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
warn(f"No se pudo eliminar volumen '{name}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_volume(name: str, labels: Optional[Dict[str,str]] = None) -> bool:
|
||||||
|
cmd = ["docker", "volume", "create"]
|
||||||
|
if labels:
|
||||||
|
for k, v in labels.items():
|
||||||
|
cmd += ["--label", f"{k}={v}"]
|
||||||
|
cmd.append(name)
|
||||||
|
try:
|
||||||
|
run(cmd, check=True, capture_output=False)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
warn(f"Fallo creando volumen '{name}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restore_into_volume(volume_name: str, backup_dir: pathlib.Path, archive_file: pathlib.Path) -> int:
|
||||||
|
bdir_abs = backup_dir.resolve()
|
||||||
|
docker_cmd = [
|
||||||
|
"docker", "run", "--rm",
|
||||||
|
"-v", f"{volume_name}:/volume",
|
||||||
|
"-v", f"{str(bdir_abs)}:/backup",
|
||||||
|
"alpine:latest",
|
||||||
|
"sh", "-lc",
|
||||||
|
f"tar xzf /backup/{shlex.quote(archive_file.name)} -C /volume"
|
||||||
|
]
|
||||||
|
proc = subprocess.run(docker_cmd)
|
||||||
|
return proc.returncode
|
||||||
|
|
||||||
|
# ---------- parsing helpers ----------
|
||||||
|
|
||||||
|
def find_backup_dirs(root: pathlib.Path) -> List[pathlib.Path]:
|
||||||
|
dirs = [p for p in root.iterdir() if p.is_dir() and BACKUP_DIR_PATTERN.match(p.name)]
|
||||||
|
dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
|
return dirs
|
||||||
|
|
||||||
|
def find_archives(dirpath: pathlib.Path) -> List[pathlib.Path]:
|
||||||
|
files = [p for p in dirpath.iterdir() if p.is_file() and p.name.endswith(".tar.gz")]
|
||||||
|
files.sort(key=lambda p: p.name)
|
||||||
|
return files
|
||||||
|
|
||||||
|
def parse_archive_basename(archive_name: str) -> Optional[str]:
|
||||||
|
m = ARCHIVE_PATTERN.match(archive_name)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
return m.group("basename")
|
||||||
|
|
||||||
|
# ---------- compose label helpers ----------
|
||||||
|
|
||||||
|
def derive_labels_auto(volume_name: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""
|
||||||
|
project = parte antes del primer '_' o '-'
|
||||||
|
short = resto luego del separador
|
||||||
|
"""
|
||||||
|
for sep in ("_", "-"):
|
||||||
|
if sep in volume_name:
|
||||||
|
idx = volume_name.find(sep)
|
||||||
|
return volume_name[:idx], volume_name[idx+1:]
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def derive_labels_with_fixed_project(volume_name: str, project: str) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Usa project fijo. Si volume_name empieza con '<project>_' o '<project>-', recorta.
|
||||||
|
"""
|
||||||
|
p = project
|
||||||
|
if volume_name.startswith(p + "_"):
|
||||||
|
return p, volume_name[len(p) + 1:]
|
||||||
|
if volume_name.startswith(p + "-"):
|
||||||
|
return p, volume_name[len(p) + 1:]
|
||||||
|
return p, volume_name
|
||||||
|
|
||||||
|
def labels_match_compose(name: str, project: str, short: str) -> bool:
|
||||||
|
labels = inspect_volume_labels(name)
|
||||||
|
return (
|
||||||
|
labels.get("com.docker.compose.project") == project and
|
||||||
|
labels.get("com.docker.compose.volume") == short
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- UI flows ----------
|
||||||
|
|
||||||
|
def pick_backup_dir(dirs: List[pathlib.Path]) -> Optional[pathlib.Path]:
|
||||||
|
if not dirs:
|
||||||
|
warn("No se encontraron carpetas de backup 'docker-volumes-<timestamp>'.")
|
||||||
|
return None
|
||||||
|
print("\nCarpetas de backup encontradas:")
|
||||||
|
for i, d in enumerate(dirs, 1):
|
||||||
|
print(f" {i}) {d.name}")
|
||||||
|
while True:
|
||||||
|
sel = input("> Elige una carpeta (número) o Enter para cancelar: ").strip()
|
||||||
|
if not sel:
|
||||||
|
return None
|
||||||
|
if sel.isdigit() and 1 <= int(sel) <= len(dirs):
|
||||||
|
return dirs[int(sel) - 1]
|
||||||
|
print("Opción inválida.")
|
||||||
|
|
||||||
|
def pick_archives(files: List[pathlib.Path]) -> List[pathlib.Path]:
|
||||||
|
if not files:
|
||||||
|
warn("No hay archivos .tar.gz en esa carpeta.")
|
||||||
|
return []
|
||||||
|
print("\nBackups disponibles:")
|
||||||
|
for i, f in enumerate(files, 1):
|
||||||
|
base = parse_archive_basename(f.name) or f.name
|
||||||
|
print(f" {i}) {f.name} -> volumen: {base}")
|
||||||
|
print("\nOpciones:")
|
||||||
|
print(" a) Restaurar TODOS")
|
||||||
|
print(" s) Seleccionar algunos (ej: 1,3,5)")
|
||||||
|
while True:
|
||||||
|
sel = input("> Elige 'a' o 's': ").strip().lower()
|
||||||
|
if sel == "a":
|
||||||
|
return files
|
||||||
|
if sel == "s":
|
||||||
|
picks = input("> Números separados por coma: ").strip()
|
||||||
|
idxs = []
|
||||||
|
try:
|
||||||
|
for tok in picks.split(","):
|
||||||
|
tok = tok.strip()
|
||||||
|
if tok:
|
||||||
|
idx = int(tok)
|
||||||
|
idxs.append(idx - 1)
|
||||||
|
chosen = [files[i] for i in sorted(set(i for i in idxs if 0 <= i < len(files)))]
|
||||||
|
if chosen:
|
||||||
|
return chosen
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print("Selección inválida.")
|
||||||
|
else:
|
||||||
|
print("Opción inválida.")
|
||||||
|
|
||||||
|
def pick_restore_mode() -> str:
|
||||||
|
print("\nModo de restauración:")
|
||||||
|
print(" 1) Tradicional (sin labels)")
|
||||||
|
print(" 2) Reconocido por Compose (aplica labels para evitar el warning)")
|
||||||
|
while True:
|
||||||
|
sel = input("> Elige 1 o 2: ").strip()
|
||||||
|
if sel in ("1", "2"):
|
||||||
|
return sel
|
||||||
|
print("Opción inválida.")
|
||||||
|
|
||||||
|
def confirm_overwrite(volume_name: str) -> bool:
|
||||||
|
return yes_no(f"El volumen '{volume_name}' ya existe. ¿Sobrescribir (recrear)?", default="n")
|
||||||
|
|
||||||
|
# ---------- restore flows ----------
|
||||||
|
|
||||||
|
def restore_traditional(backup_dir: pathlib.Path, archives: List[pathlib.Path]):
|
||||||
|
ensure_alpine_image()
|
||||||
|
print("\n=== Restauración TRADICIONAL ===\n")
|
||||||
|
for arch in archives:
|
||||||
|
vname = parse_archive_basename(arch.name)
|
||||||
|
if not vname:
|
||||||
|
warn(f"Nombre de backup no reconocible: {arch.name}, se omite.")
|
||||||
|
continue
|
||||||
|
info(f"Volumen: {vname}")
|
||||||
|
|
||||||
|
# Tradicional: no cambiamos labels; si existe, restauramos sobre volumen nuevo (recreándolo)
|
||||||
|
if volume_exists(vname):
|
||||||
|
# Intentar eliminar: si está en uso, ofrecer detener/remover contenedores
|
||||||
|
if not confirm_overwrite(vname):
|
||||||
|
info(" → Omitido (ya existe).")
|
||||||
|
continue
|
||||||
|
ids = containers_using_volume(vname)
|
||||||
|
if ids:
|
||||||
|
info(f"Contenedores que usan '{vname}': {', '.join(ids)}")
|
||||||
|
if yes_no("¿Detener y eliminar esos contenedores para continuar?", default="y"):
|
||||||
|
stop_containers(ids)
|
||||||
|
remove_containers(ids)
|
||||||
|
else:
|
||||||
|
warn(" → No se puede recrear el volumen en uso. Omitido.")
|
||||||
|
continue
|
||||||
|
if not remove_volume(vname):
|
||||||
|
warn(" → No se pudo eliminar el volumen. Omitido.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not create_volume(vname):
|
||||||
|
warn(" → No se pudo crear el volumen, se omite.")
|
||||||
|
continue
|
||||||
|
rc = restore_into_volume(vname, backup_dir, arch)
|
||||||
|
if rc == 0:
|
||||||
|
ok(" Restaurado.")
|
||||||
|
else:
|
||||||
|
warn(f" Falló la restauración (rc={rc}).")
|
||||||
|
|
||||||
|
def restore_with_compose_labels(backup_dir: pathlib.Path, archives: List[pathlib.Path]):
|
||||||
|
"""
|
||||||
|
Restaura creando volúmenes con labels de Compose para que NO aparezca el warning:
|
||||||
|
"volume ... already exists but was not created by Docker Compose..."
|
||||||
|
"""
|
||||||
|
ensure_alpine_image()
|
||||||
|
print("\n=== Restauración RECONOCIDA POR COMPOSE (con labels) ===\n")
|
||||||
|
print("Estrategia de etiquetado:")
|
||||||
|
print(" 1) Auto (project = prefijo de <vol> antes de '_' o '-', short = resto)")
|
||||||
|
print(" 2) Fijar un 'project' para todos (p. ej. suitecoffee, suitecoffee_dev, suitecoffee_prod)")
|
||||||
|
mode = ""
|
||||||
|
while mode not in ("1", "2"):
|
||||||
|
mode = input("> Elige 1 o 2: ").strip()
|
||||||
|
|
||||||
|
fixed_project = None
|
||||||
|
if mode == "2":
|
||||||
|
fixed_project = input("> Indica el 'project' de Compose (exacto): ").strip()
|
||||||
|
if not fixed_project:
|
||||||
|
warn("Project vacío, cancelado.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Previsualización de etiquetas
|
||||||
|
preview = []
|
||||||
|
for arch in archives:
|
||||||
|
vname = parse_archive_basename(arch.name)
|
||||||
|
if not vname:
|
||||||
|
continue
|
||||||
|
if mode == "1":
|
||||||
|
proj, short = derive_labels_auto(vname)
|
||||||
|
else:
|
||||||
|
proj, short = derive_labels_with_fixed_project(vname, fixed_project)
|
||||||
|
preview.append((arch, vname, proj, short))
|
||||||
|
|
||||||
|
print("\nVista previa de etiquetas (project / volume):")
|
||||||
|
for _, vname, proj, short in preview:
|
||||||
|
if proj and short:
|
||||||
|
print(f" {vname} → project='{proj}', volume='{short}'")
|
||||||
|
else:
|
||||||
|
print(f" {vname} → (no derivado; se pedirá manualmente)")
|
||||||
|
|
||||||
|
if not yes_no("\n¿Confirmar restauración con estas etiquetas?", default="y"):
|
||||||
|
warn("Cancelado por el usuario.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Restaurar con labels
|
||||||
|
for arch, vname, proj, short in preview:
|
||||||
|
# completar manual si falta
|
||||||
|
if not proj or not short:
|
||||||
|
print(f"\nDefinir etiquetas para: {vname}")
|
||||||
|
proj = input(" project = ").strip()
|
||||||
|
short = input(" volume = ").strip()
|
||||||
|
if not proj or not short:
|
||||||
|
warn(" → Etiquetas incompletas; se omite.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
info(f"\nVolumen: {vname} (labels: project='{proj}', volume='{short}')")
|
||||||
|
|
||||||
|
if volume_exists(vname):
|
||||||
|
# ¿ya tiene labels correctas? entonces solo restauramos datos sin recrear
|
||||||
|
if labels_match_compose(vname, proj, short):
|
||||||
|
info(" Volumen ya tiene labels de Compose correctas. Sobrescribiendo datos...")
|
||||||
|
rc = restore_into_volume(vname, backup_dir, arch)
|
||||||
|
if rc == 0:
|
||||||
|
ok(" Restaurado (labels ya correctas).")
|
||||||
|
else:
|
||||||
|
warn(f" Falló la restauración (rc={rc}).")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Pedir permiso para detener/eliminar contenedores y recrear volumen con labels correctas
|
||||||
|
if not yes_no(" El volumen existe sin labels correctas. ¿Detener/eliminar contenedores y recrearlo con labels para evitar el warning?", default="y"):
|
||||||
|
warn(" → Omitido (mantiene warning de Compose).")
|
||||||
|
continue
|
||||||
|
|
||||||
|
ids = containers_using_volume(vname)
|
||||||
|
if ids:
|
||||||
|
info(f" Contenedores que usan '{vname}': {', '.join(ids)}")
|
||||||
|
stop_containers(ids)
|
||||||
|
remove_containers(ids)
|
||||||
|
|
||||||
|
if not remove_volume(vname):
|
||||||
|
warn(" → No se pudo eliminar el volumen. Omitido.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"com.docker.compose.project": proj,
|
||||||
|
"com.docker.compose.volume": short,
|
||||||
|
}
|
||||||
|
if not create_volume(vname, labels=labels):
|
||||||
|
warn(" → No se pudo crear el volumen con labels. Omitido.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
rc = restore_into_volume(vname, backup_dir, arch)
|
||||||
|
if rc == 0:
|
||||||
|
ok(" Restaurado con labels de Compose (warning resuelto).")
|
||||||
|
else:
|
||||||
|
warn(f" Falló la restauración (rc={rc}).")
|
||||||
|
|
||||||
|
# ---------- main ----------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not which("docker"):
|
||||||
|
fail("No se encontró 'docker' en el PATH.")
|
||||||
|
try:
|
||||||
|
run(["docker", "version"], check=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
fail("No se puede comunicar con el daemon de Docker. ¿Está corriendo?")
|
||||||
|
|
||||||
|
# Elegir carpeta docker-volumes-<ts>
|
||||||
|
dirs = [p for p in PROJECT_ROOT.iterdir() if p.is_dir() and BACKUP_DIR_PATTERN.match(p.name)]
|
||||||
|
dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
|
if not dirs:
|
||||||
|
warn("No hay carpetas de backup 'docker-volumes-<timestamp>'.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nCarpetas de backup encontradas:")
|
||||||
|
for i, d in enumerate(dirs, 1):
|
||||||
|
print(f" {i}) {d.name}")
|
||||||
|
chosen = None
|
||||||
|
while not chosen:
|
||||||
|
sel = input("> Elige una carpeta (número) o Enter para salir: ").strip()
|
||||||
|
if not sel:
|
||||||
|
warn("Cancelado."); return
|
||||||
|
if sel.isdigit() and 1 <= int(sel) <= len(dirs):
|
||||||
|
chosen = dirs[int(sel)-1]
|
||||||
|
else:
|
||||||
|
print("Opción inválida.")
|
||||||
|
|
||||||
|
# Archivos en carpeta
|
||||||
|
archives = [p for p in chosen.iterdir() if p.is_file() and p.name.endswith(".tar.gz")]
|
||||||
|
archives.sort(key=lambda p: p.name)
|
||||||
|
if not archives:
|
||||||
|
warn("No hay .tar.gz en esa carpeta."); return
|
||||||
|
|
||||||
|
print("\nBackups disponibles:")
|
||||||
|
for i, f in enumerate(archives, 1):
|
||||||
|
base = parse_archive_basename(f.name) or f.name
|
||||||
|
print(f" {i}) {f.name} -> volumen: {base}")
|
||||||
|
|
||||||
|
print("\nOpciones de selección:")
|
||||||
|
print(" a) Restaurar TODOS")
|
||||||
|
print(" s) Elegir algunos (ej: 1,3,5)")
|
||||||
|
selected: List[pathlib.Path] = []
|
||||||
|
while not selected:
|
||||||
|
mode = input("> Elige 'a' o 's': ").strip().lower()
|
||||||
|
if mode == "a":
|
||||||
|
selected = archives
|
||||||
|
elif mode == "s":
|
||||||
|
picks = input("> Números separados por coma: ").strip()
|
||||||
|
try:
|
||||||
|
idxs = [int(x.strip())-1 for x in picks.split(",") if x.strip()]
|
||||||
|
selected = [archives[i] for i in sorted(set(i for i in idxs if 0 <= i < len(archives)))]
|
||||||
|
except Exception:
|
||||||
|
selected = []
|
||||||
|
else:
|
||||||
|
print("Opción inválida.")
|
||||||
|
|
||||||
|
# Modo de restauración
|
||||||
|
choice = pick_restore_mode()
|
||||||
|
if choice == "1":
|
||||||
|
restore_traditional(chosen, selected)
|
||||||
|
else:
|
||||||
|
restore_with_compose_labels(chosen, selected)
|
||||||
|
|
||||||
|
ok("\nProceso finalizado.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n✓ Interrumpido por el usuario (Ctrl+C).")
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Dockerfile.dev
|
# Dockerfile.dev
|
||||||
FROM node:20.17
|
FROM node:22.18
|
||||||
|
|
||||||
# Definir variables de entorno con valores predeterminados
|
# Definir variables de entorno con valores predeterminados
|
||||||
ARG NODE_ENV=development
|
# ARG NODE_ENV=production
|
||||||
ARG PORT=3000
|
# ARG PORT=3000
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||||
@@ -17,8 +17,4 @@ RUN npm i
|
|||||||
# Copia el resto de la app
|
# Copia el resto de la app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expone el puerto
|
CMD ["npm", "run", "start"]
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Usa nodemon para hot reload si lo tenés
|
|
||||||
CMD ["npm", "run", "dev"]
|
|
||||||
Generated
+142
-24
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "aplication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app",
|
"name": "aplication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@@ -26,12 +26,14 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
||||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-types": "^3.0.0",
|
"mime-types": "^3.0.0",
|
||||||
"negotiator": "^1.0.0"
|
"negotiator": "^1.0.0"
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"picomatch": "^2.0.4"
|
"picomatch": "^2.0.4"
|
||||||
@@ -57,13 +60,15 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
@@ -75,6 +80,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
@@ -95,6 +101,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -105,6 +112,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
},
|
},
|
||||||
@@ -116,6 +124,7 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -124,6 +133,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -136,6 +146,7 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
"get-intrinsic": "^1.3.0"
|
"get-intrinsic": "^1.3.0"
|
||||||
@@ -148,9 +159,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
|
||||||
"integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==",
|
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
@@ -164,6 +175,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
@@ -187,12 +199,14 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||||
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "5.2.1"
|
"safe-buffer": "5.2.1"
|
||||||
},
|
},
|
||||||
@@ -204,6 +218,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -212,6 +227,7 @@
|
|||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -220,6 +236,7 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.6.0"
|
"node": ">=6.6.0"
|
||||||
}
|
}
|
||||||
@@ -228,6 +245,7 @@
|
|||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"object-assign": "^4",
|
"object-assign": "^4",
|
||||||
"vary": "^1"
|
"vary": "^1"
|
||||||
@@ -259,6 +277,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
"shebang-command": "^2.0.0",
|
"shebang-command": "^2.0.0",
|
||||||
@@ -272,6 +291,7 @@
|
|||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
},
|
},
|
||||||
@@ -288,6 +308,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -296,6 +317,7 @@
|
|||||||
"version": "17.2.1",
|
"version": "17.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -307,6 +329,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -319,12 +342,14 @@
|
|||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -333,6 +358,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -341,6 +367,7 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -349,6 +376,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -359,12 +387,14 @@
|
|||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/etag": {
|
"node_modules/etag": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -373,6 +403,7 @@
|
|||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
@@ -420,6 +451,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
},
|
},
|
||||||
@@ -431,6 +463,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"encodeurl": "^2.0.0",
|
"encodeurl": "^2.0.0",
|
||||||
@@ -447,6 +480,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -455,6 +489,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -465,6 +500,7 @@
|
|||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@@ -477,6 +513,7 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -485,6 +522,7 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
"es-define-property": "^1.0.1",
|
"es-define-property": "^1.0.1",
|
||||||
@@ -508,6 +546,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
"es-object-atoms": "^1.0.0"
|
"es-object-atoms": "^1.0.0"
|
||||||
@@ -521,6 +560,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
},
|
},
|
||||||
@@ -532,6 +572,7 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -544,6 +585,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
@@ -552,6 +594,7 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -563,6 +606,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -574,6 +618,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
"inherits": "2.0.4",
|
"inherits": "2.0.4",
|
||||||
@@ -589,6 +634,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -597,6 +643,7 @@
|
|||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
},
|
},
|
||||||
@@ -608,17 +655,20 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
@@ -628,6 +678,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -640,6 +691,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -649,6 +701,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
},
|
},
|
||||||
@@ -661,6 +714,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
@@ -668,18 +722,21 @@
|
|||||||
"node_modules/is-promise": {
|
"node_modules/is-promise": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -688,6 +745,7 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -696,6 +754,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -707,6 +766,7 @@
|
|||||||
"version": "1.54.0",
|
"version": "1.54.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -715,6 +775,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "^1.54.0"
|
"mime-db": "^1.54.0"
|
||||||
},
|
},
|
||||||
@@ -727,6 +788,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
@@ -737,12 +799,14 @@
|
|||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -781,6 +845,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -789,6 +854,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -797,6 +863,7 @@
|
|||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -808,6 +875,7 @@
|
|||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ee-first": "1.1.1"
|
"ee-first": "1.1.1"
|
||||||
},
|
},
|
||||||
@@ -819,6 +887,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@@ -827,6 +896,7 @@
|
|||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -836,6 +906,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -844,6 +915,7 @@
|
|||||||
"version": "8.2.0",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
@@ -852,6 +924,7 @@
|
|||||||
"version": "8.16.3",
|
"version": "8.16.3",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.9.1",
|
"pg-connection-string": "^2.9.1",
|
||||||
"pg-pool": "^3.10.1",
|
"pg-pool": "^3.10.1",
|
||||||
@@ -878,12 +951,14 @@
|
|||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||||
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||||
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/pg-connection-string": {
|
"node_modules/pg-connection-string": {
|
||||||
"version": "2.9.1",
|
"version": "2.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||||
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="
|
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-format": {
|
"node_modules/pg-format": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -898,6 +973,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
@@ -906,6 +982,7 @@
|
|||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||||
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"pg": ">=8.0"
|
"pg": ">=8.0"
|
||||||
}
|
}
|
||||||
@@ -913,12 +990,14 @@
|
|||||||
"node_modules/pg-protocol": {
|
"node_modules/pg-protocol": {
|
||||||
"version": "1.10.3",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
||||||
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="
|
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-types": {
|
"node_modules/pg-types": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-int8": "1.0.1",
|
"pg-int8": "1.0.1",
|
||||||
"postgres-array": "~2.0.0",
|
"postgres-array": "~2.0.0",
|
||||||
@@ -934,6 +1013,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"split2": "^4.1.0"
|
"split2": "^4.1.0"
|
||||||
}
|
}
|
||||||
@@ -943,6 +1023,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
},
|
},
|
||||||
@@ -954,6 +1035,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
@@ -962,6 +1044,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -970,6 +1053,7 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -978,6 +1062,7 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -989,6 +1074,7 @@
|
|||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"forwarded": "0.2.0",
|
"forwarded": "0.2.0",
|
||||||
"ipaddr.js": "1.9.1"
|
"ipaddr.js": "1.9.1"
|
||||||
@@ -1001,12 +1087,14 @@
|
|||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"side-channel": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -1021,6 +1109,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -1029,6 +1118,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
@@ -1044,6 +1134,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
},
|
},
|
||||||
@@ -1055,6 +1146,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"depd": "^2.0.0",
|
"depd": "^2.0.0",
|
||||||
@@ -1083,18 +1175,21 @@
|
|||||||
"type": "consulting",
|
"type": "consulting",
|
||||||
"url": "https://feross.org/support"
|
"url": "https://feross.org/support"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safer-buffer": {
|
"node_modules/safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.2",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
},
|
},
|
||||||
@@ -1106,6 +1201,7 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.5",
|
"debug": "^4.3.5",
|
||||||
"encodeurl": "^2.0.0",
|
"encodeurl": "^2.0.0",
|
||||||
@@ -1127,6 +1223,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encodeurl": "^2.0.0",
|
"encodeurl": "^2.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
@@ -1140,13 +1237,15 @@
|
|||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
},
|
},
|
||||||
@@ -1159,6 +1258,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -1167,6 +1267,7 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"object-inspect": "^1.13.3",
|
"object-inspect": "^1.13.3",
|
||||||
@@ -1185,6 +1286,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"object-inspect": "^1.13.3"
|
"object-inspect": "^1.13.3"
|
||||||
@@ -1200,6 +1302,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -1217,6 +1320,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -1236,6 +1340,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||||
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": "^7.5.3"
|
"semver": "^7.5.3"
|
||||||
},
|
},
|
||||||
@@ -1247,6 +1352,7 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.x"
|
"node": ">= 10.x"
|
||||||
}
|
}
|
||||||
@@ -1255,6 +1361,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -1264,6 +1371,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
},
|
},
|
||||||
@@ -1276,6 +1384,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
},
|
},
|
||||||
@@ -1287,6 +1396,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
@@ -1296,6 +1406,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
||||||
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"nodetouch": "bin/nodetouch.js"
|
"nodetouch": "bin/nodetouch.js"
|
||||||
}
|
}
|
||||||
@@ -1304,6 +1415,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"media-typer": "^1.1.0",
|
"media-typer": "^1.1.0",
|
||||||
@@ -1317,12 +1429,14 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -1331,6 +1445,7 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -1340,6 +1455,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -1353,12 +1469,14 @@
|
|||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "suitecoffee_aplication_service",
|
"name": "aplication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=production node ./src/index.js",
|
"start": "NODE_ENV=production node ./src/index.js",
|
||||||
"dev": "NODE_ENV=development npx nodemon ./src/index.js",
|
"dev": "NODE_ENV=development npx nodemon ./src/index.js",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"nodemon": "^3.1.10"
|
"nodemon": "^3.1.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ try {
|
|||||||
console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
|
console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
|
||||||
} else if (process.env.NODE_ENV === 'stage') {
|
} else if (process.env.NODE_ENV === 'stage') {
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
||||||
console.log(`Activando entorno de ->->${chalk.yellow(` TESTING `)}`);
|
console.log(`Activando entorno de ->${chalk.yellow(` TESTING `)}`);
|
||||||
} else if (process.env.NODE_ENV === 'production') {
|
} else if (process.env.NODE_ENV === 'production') {
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
|
dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
|
||||||
console.log(`Activando entorno de ->->${chalk.red(` PRODUCTION `)}`);
|
console.log(`Activando entorno de ->${chalk.red(` PRODUCTION `)}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
||||||
@@ -34,6 +34,7 @@ try {
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(express.static(path.join(__dirname, 'pages')));
|
||||||
|
|
||||||
// Configuración de conexión PostgreSQL
|
// Configuración de conexión PostgreSQL
|
||||||
|
|
||||||
@@ -64,7 +65,6 @@ async function verificarConexion() {
|
|||||||
|
|
||||||
|
|
||||||
// === Servir páginas estáticas ===
|
// === Servir páginas estáticas ===
|
||||||
app.use(express.static(path.join(__dirname, 'pages')));
|
|
||||||
|
|
||||||
app.get('/roles', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'roles.html')));
|
app.get('/roles', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'roles.html')));
|
||||||
app.get('/usuarios', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'usuarios.html')));
|
app.get('/usuarios', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'usuarios.html')));
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Dockerfile.dev
|
# Dockerfile.dev
|
||||||
FROM node:20.17
|
FROM node:22.18
|
||||||
|
|
||||||
# Definir variables de entorno con valores predeterminados
|
# Definir variables de entorno con valores predeterminados
|
||||||
ARG NODE_ENV=development
|
# ARG NODE_ENV=production
|
||||||
ARG PORT=4000
|
# ARG PORT=4000
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||||
@@ -17,8 +17,4 @@ RUN npm i
|
|||||||
# Copia el resto de la app
|
# Copia el resto de la app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expone el puerto
|
CMD ["npm", "run", "start"]
|
||||||
EXPOSE 4000
|
|
||||||
|
|
||||||
# Usa nodemon para hot reload si lo tenés
|
|
||||||
CMD ["npm", "run", "dev"]
|
|
||||||
Generated
+6
-6
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "auth",
|
"name": "authentication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "auth",
|
"name": "authentication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@@ -239,9 +239,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
|
||||||
"integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==",
|
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "suitecoffee_authentication_service",
|
"name": "authentication",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=production node ./src/index.js",
|
"start": "NODE_ENV=production node ./src/index.js",
|
||||||
"dev": "NODE_ENV=development npx nodemon ./src/index.js",
|
"dev": "NODE_ENV=development npx nodemon ./src/index.js",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// auth/src/index.js
|
// auth/src/index.js
|
||||||
import chalk from 'chalk'; // Colores!
|
import chalk from 'chalk';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import expressLayouts from 'express-ejs-layouts';
|
import expressLayouts from 'express-ejs-layouts';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
@@ -22,20 +22,22 @@ try {
|
|||||||
console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
|
console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`);
|
||||||
} else if (process.env.NODE_ENV === 'stage') {
|
} else if (process.env.NODE_ENV === 'stage') {
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
dotenv.config({ path: path.resolve(__dirname, '../.env.test' )});
|
||||||
console.log(`Activando entorno de ->->${chalk.yellow(` TESTING `)}`);
|
console.log(`Activando entorno de ->${chalk.yellow(` TESTING `)}`);
|
||||||
} else if (process.env.NODE_ENV === 'production') {
|
} else if (process.env.NODE_ENV === 'production') {
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
|
dotenv.config({ path: path.resolve(__dirname, '../.env.production' )});
|
||||||
console.log(`Activando entorno de ->->${chalk.red(` PRODUCTION `)}`);
|
console.log(`Activando entorno de ->${chalk.red(` PRODUCTION `)}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderiado
|
// Configuración de renderizado
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', true);
|
||||||
|
app.use(express.static(path.join(__dirname, 'pages')));
|
||||||
|
|
||||||
|
|
||||||
// Configuración de conexión PostgreSQL
|
// Configuración de conexión PostgreSQL
|
||||||
|
|
||||||
@@ -64,9 +66,9 @@ async function verificarConexion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// === Servir páginas estáticas ===
|
// === Servir páginas estáticas ===
|
||||||
app.use('/auth', express.static(path.join(__dirname, 'pages')));
|
|
||||||
|
app.get('/',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'index.html')));
|
||||||
|
|
||||||
app.get('/planes', async (req, res) => {
|
app.get('/planes', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -83,11 +85,6 @@ app.get('/planes', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ruta raíz
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.sendFile(path.join(__dirname, 'pages', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/registro', async (req, res) => {
|
app.post('/api/registro', async (req, res) => {
|
||||||
const {
|
const {
|
||||||
nombre_empresa,
|
nombre_empresa,
|
||||||
|
|||||||
@@ -53,7 +53,6 @@
|
|||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
<button class="btn btn-link btn-sm" id="toggle-mode">¿No tienes cuenta? Regístrate</button>
|
<button class="btn btn-link btn-sm" id="toggle-mode">¿No tienes cuenta? Regístrate</button>
|
||||||
</div>
|
</div>
|
||||||
<p>Hola!</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+271
-161
@@ -8,98 +8,81 @@ from shutil import which
|
|||||||
|
|
||||||
PROJECT_ROOT = os.path.abspath(os.getcwd())
|
PROJECT_ROOT = os.path.abspath(os.getcwd())
|
||||||
|
|
||||||
# Archivos comunes
|
# === Archivos Compose (entornos) ===
|
||||||
BASE_COMPOSE = os.path.join(PROJECT_ROOT, "docker-compose.yml")
|
BASE_COMPOSE = os.path.join(PROJECT_ROOT, "compose.yaml")
|
||||||
OVERRIDE_COMPOSE = os.path.join(PROJECT_ROOT, "docker-compose.override.yml")
|
DEV_COMPOSE = os.path.join(PROJECT_ROOT, "compose.dev.yaml")
|
||||||
|
PROD_COMPOSE = os.path.join(PROJECT_ROOT, "compose.prod.yaml")
|
||||||
|
|
||||||
# Mapeo de entornos -> archivo .env
|
# === Archivos Compose (globales) ===
|
||||||
|
NPM_COMPOSE = os.path.join(PROJECT_ROOT, "compose.npm.yaml")
|
||||||
|
DBEAVER_COMPOSE = os.path.join(PROJECT_ROOT, "compose.dbeaver.yaml")
|
||||||
|
|
||||||
|
# Archivos .env
|
||||||
ENV_FILES = {
|
ENV_FILES = {
|
||||||
"development": ".env.development",
|
"development": ".env.development",
|
||||||
"production": ".env.production",
|
"production": ".env.production",
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- Nuevas utilidades ----------
|
# Nombres de proyecto para permitir DEV y PROD simultáneos
|
||||||
|
def _base_project():
|
||||||
|
return os.path.basename(PROJECT_ROOT).lower() or "composeproj"
|
||||||
|
|
||||||
def resolve_project_name(env_file=None, include_override=True):
|
PROJECT_NAMES = {
|
||||||
"""
|
"development": f"{_base_project()}_dev",
|
||||||
Obtiene el 'project name' que usará docker compose para esta combinación de archivos/env,
|
"production": f"{_base_project()}_prod",
|
||||||
consultando a 'docker compose config --format json'. Si falla, usa el nombre de la carpeta.
|
}
|
||||||
"""
|
|
||||||
cmd = ["docker", "compose"] + compose_files_args(include_override=include_override)
|
|
||||||
if env_file:
|
|
||||||
cmd += ["--env-file", env_file]
|
|
||||||
cmd += ["config", "--format", "json"]
|
|
||||||
proc = run(cmd, capture_output=True)
|
|
||||||
if proc.returncode == 0:
|
|
||||||
try:
|
|
||||||
data = json.loads(proc.stdout)
|
|
||||||
# Compose v2 devuelve 'name' en el JSON; si no, fallback
|
|
||||||
return data.get("name") or os.path.basename(PROJECT_ROOT)
|
|
||||||
except Exception:
|
|
||||||
return os.path.basename(PROJECT_ROOT)
|
|
||||||
return os.path.basename(PROJECT_ROOT)
|
|
||||||
|
|
||||||
def list_project_containers(project_name, all_states=True):
|
# Nombre de proyecto global (ambos yaml globales usan name: suitecoffee)
|
||||||
"""
|
GLOBAL_PROJECT_NAME = "suitecoffee"
|
||||||
Lista contenedores del proyecto por etiqueta com.docker.compose.project=<name>.
|
|
||||||
Si all_states=False, solo los running.
|
|
||||||
Devuelve lista de dicts con {id, name, status, image}.
|
|
||||||
"""
|
|
||||||
base = ["docker", "ps"]
|
|
||||||
if all_states:
|
|
||||||
base.append("-a")
|
|
||||||
base += ["--filter", f"label=com.docker.compose.project={project_name}",
|
|
||||||
"--format", "{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}"]
|
|
||||||
proc = run(base, capture_output=True)
|
|
||||||
if proc.returncode != 0:
|
|
||||||
return []
|
|
||||||
rows = []
|
|
||||||
for line in proc.stdout.splitlines():
|
|
||||||
parts = line.strip().split("\t")
|
|
||||||
if len(parts) >= 4:
|
|
||||||
rows.append({"id": parts[0], "name": parts[1], "status": parts[2], "image": parts[3]})
|
|
||||||
return rows
|
|
||||||
|
|
||||||
def print_containers_table(title, rows):
|
|
||||||
print_header(title)
|
|
||||||
if not rows:
|
|
||||||
print("(ninguno)\n")
|
|
||||||
return
|
|
||||||
# ancho simple, sin dependencias
|
|
||||||
print(f"{'ID':<12} {'NAME':<40} {'STATUS':<20} IMAGE")
|
|
||||||
for r in rows:
|
|
||||||
print(f"{r['id']:<12} {r['name']:<40} {r['status']:<20} {r['image']}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------- Utilidades ----------
|
# ---------- Utilidades ----------
|
||||||
|
|
||||||
def check_prereqs():
|
def check_prereqs():
|
||||||
if which("docker") is None:
|
if which("docker") is None:
|
||||||
fail("No se encontró 'docker' en el PATH.")
|
fail("No se encontró 'docker' en el PATH.")
|
||||||
# Verificar que docker compose esté disponible (subcomando integrado)
|
|
||||||
try:
|
try:
|
||||||
run(["docker", "compose", "version"], check=True, capture_output=True)
|
run(["docker", "compose", "version"], check=True, capture_output=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
fail("No se pudo ejecutar 'docker compose'. Asegúrate de tener Docker Compose v2.")
|
fail("No se pudo ejecutar 'docker compose'. Asegúrate de tener Docker Compose v2.")
|
||||||
|
|
||||||
def run(cmd, check=False, capture_output=False):
|
def run(cmd, check=False, capture_output=False):
|
||||||
return subprocess.run(
|
return subprocess.run(cmd, check=check, capture_output=capture_output, text=True)
|
||||||
cmd,
|
|
||||||
check=check,
|
|
||||||
capture_output=capture_output,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def compose_files_args(include_override=True):
|
def compose_files_args(env_key):
|
||||||
args = []
|
"""
|
||||||
if os.path.exists(BASE_COMPOSE):
|
Devuelve los -f correctos según el entorno (dev/prod) + base.
|
||||||
args += ["-f", BASE_COMPOSE]
|
"""
|
||||||
|
if not os.path.exists(BASE_COMPOSE):
|
||||||
|
fail("No se encontró compose.yaml en la raíz del proyecto.")
|
||||||
|
|
||||||
|
args = ["-f", BASE_COMPOSE]
|
||||||
|
|
||||||
|
if env_key == "development":
|
||||||
|
if not os.path.exists(DEV_COMPOSE):
|
||||||
|
fail("No se encontró compose.dev.yaml.")
|
||||||
|
args += ["-f", DEV_COMPOSE]
|
||||||
|
elif env_key == "production":
|
||||||
|
if not os.path.exists(PROD_COMPOSE):
|
||||||
|
fail("No se encontró compose.prod.yaml.")
|
||||||
|
args += ["-f", PROD_COMPOSE]
|
||||||
else:
|
else:
|
||||||
fail("No se encontró docker-compose.yml en la raíz del proyecto.")
|
fail(f"Entorno desconocido: {env_key}")
|
||||||
if include_override and os.path.exists(OVERRIDE_COMPOSE):
|
|
||||||
args += ["-f", OVERRIDE_COMPOSE]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
def compose_files_args_global(kind):
|
||||||
|
"""
|
||||||
|
Devuelve los -f correctos para servicios globales (npm/dbeaver).
|
||||||
|
"""
|
||||||
|
if kind == "npm":
|
||||||
|
if not os.path.exists(NPM_COMPOSE):
|
||||||
|
fail("No se encontró compose.npm.yaml.")
|
||||||
|
return ["-f", NPM_COMPOSE]
|
||||||
|
elif kind == "dbeaver":
|
||||||
|
if not os.path.exists(DBEAVER_COMPOSE):
|
||||||
|
fail("No se encontró compose.dbeaver.yaml.")
|
||||||
|
return ["-f", DBEAVER_COMPOSE]
|
||||||
|
else:
|
||||||
|
fail(f"Servicio global desconocido: {kind}")
|
||||||
|
|
||||||
def env_file_path(env_key):
|
def env_file_path(env_key):
|
||||||
fname = ENV_FILES.get(env_key)
|
fname = ENV_FILES.get(env_key)
|
||||||
if not fname:
|
if not fname:
|
||||||
@@ -107,33 +90,33 @@ def env_file_path(env_key):
|
|||||||
path = os.path.join(PROJECT_ROOT, fname)
|
path = os.path.join(PROJECT_ROOT, fname)
|
||||||
return path if os.path.exists(path) else None
|
return path if os.path.exists(path) else None
|
||||||
|
|
||||||
def compose_cmd(base_args, env_file=None, include_override=True):
|
def compose_cmd(base_args, env_key, env_file=None, project_name=None):
|
||||||
"""
|
"""
|
||||||
Construye el comando docker compose con los -f adecuados
|
Construye: docker compose -f base -f env --env-file ... -p <name> <COMANDO> [OPCIONES]
|
||||||
y opcionalmente --env-file si existe (antes del subcomando).
|
(importante: --env-file y -p son opciones globales y van antes del subcomando)
|
||||||
"""
|
"""
|
||||||
cmd = ["docker", "compose"]
|
cmd = ["docker", "compose"]
|
||||||
cmd += compose_files_args(include_override=include_override)
|
cmd += compose_files_args(env_key)
|
||||||
if env_file:
|
if env_file:
|
||||||
cmd += ["--env-file", env_file] # opción global antes del subcomando
|
cmd += ["--env-file", env_file]
|
||||||
|
if project_name:
|
||||||
|
cmd += ["-p", project_name]
|
||||||
|
cmd += base_args # ["up","-d","--force-recreate"] o ["ps","--status","running","-q"]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
def compose_cmd_global(base_args, kind, project_name=GLOBAL_PROJECT_NAME):
|
||||||
|
"""
|
||||||
|
Comandos para servicios globales (npm/dbeaver):
|
||||||
|
docker compose -f compose.<kind>.yaml -p suitecoffee <COMANDO> ...
|
||||||
|
"""
|
||||||
|
cmd = ["docker", "compose"]
|
||||||
|
cmd += compose_files_args_global(kind)
|
||||||
|
if project_name:
|
||||||
|
cmd += ["-p", project_name]
|
||||||
cmd += base_args
|
cmd += base_args
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def get_running_containers():
|
|
||||||
"""
|
|
||||||
Devuelve lista de container IDs en estado 'running' para este proyecto.
|
|
||||||
"""
|
|
||||||
cmd = compose_cmd(["ps", "--status", "running", "-q"])
|
|
||||||
proc = run(cmd, capture_output=True)
|
|
||||||
if proc.returncode != 0:
|
|
||||||
return []
|
|
||||||
lines = [l.strip() for l in proc.stdout.splitlines() if l.strip()]
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def yes_no(prompt, default="n"):
|
def yes_no(prompt, default="n"):
|
||||||
"""
|
|
||||||
Pregunta si/no. default: 'y' o 'n'
|
|
||||||
"""
|
|
||||||
default = default.lower()
|
default = default.lower()
|
||||||
hint = "[Y/n]" if default == "y" else "[y/N]"
|
hint = "[Y/n]" if default == "y" else "[y/N]"
|
||||||
while True:
|
while True:
|
||||||
@@ -158,13 +141,46 @@ def fail(msg):
|
|||||||
print(f"✗ {msg}")
|
print(f"✗ {msg}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# ---------- Acciones ----------
|
# ---------- Helpers globales (servicios por archivo y ps por labels) ----------
|
||||||
|
def list_services_from_compose_file(compose_path):
|
||||||
|
"""Obtiene la lista de servicios definidos en un archivo compose específico."""
|
||||||
|
cmd = ["docker", "compose", "-f", compose_path, "config", "--services"]
|
||||||
|
proc = run(cmd, capture_output=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return []
|
||||||
|
return [s.strip() for s in proc.stdout.splitlines() if s.strip()]
|
||||||
|
|
||||||
def bring_up(env_key, include_override=True):
|
def list_services_global(kind):
|
||||||
|
compose_path = NPM_COMPOSE if kind == "npm" else DBEAVER_COMPOSE
|
||||||
|
return list_services_from_compose_file(compose_path)
|
||||||
|
|
||||||
|
def docker_ps_by_labels(project, service=None, running_only=True):
|
||||||
|
"""Lista contenedores por labels de compose (project/service)."""
|
||||||
|
cmd = ["docker", "ps"]
|
||||||
|
if running_only:
|
||||||
|
# por defecto docker ps ya lista solo running; se deja explícito por claridad
|
||||||
|
pass
|
||||||
|
cmd += ["--filter", f"label=com.docker.compose.project={project}"]
|
||||||
|
if service:
|
||||||
|
cmd += ["--filter", f"label=com.docker.compose.service={service}"]
|
||||||
|
cmd += ["-q"]
|
||||||
|
proc = run(cmd, capture_output=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return []
|
||||||
|
return [l.strip() for l in proc.stdout.splitlines() if l.strip()]
|
||||||
|
|
||||||
|
# ---------- Acciones (entornos) ----------
|
||||||
|
def bring_up(env_key, force_recreate=False):
|
||||||
env_path = env_file_path(env_key)
|
env_path = env_file_path(env_key)
|
||||||
if not env_path:
|
if not env_path:
|
||||||
warn(f"No se encontró archivo de entorno para '{env_key}'. Continuando sin --env-file.")
|
warn(f"No se encontró archivo de entorno para '{env_key}'. Continuando sin --env-file.")
|
||||||
cmd = compose_cmd(["up", "-d"], env_file=env_path, include_override=include_override)
|
pname = PROJECT_NAMES.get(env_key)
|
||||||
|
|
||||||
|
base_args = ["up", "-d"]
|
||||||
|
if force_recreate:
|
||||||
|
base_args.append("--force-recreate")
|
||||||
|
|
||||||
|
cmd = compose_cmd(base_args, env_key=env_key, env_file=env_path, project_name=pname)
|
||||||
info("Ejecutando: " + " ".join(cmd))
|
info("Ejecutando: " + " ".join(cmd))
|
||||||
proc = run(cmd)
|
proc = run(cmd)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
@@ -172,98 +188,192 @@ def bring_up(env_key, include_override=True):
|
|||||||
else:
|
else:
|
||||||
fail(f"Fallo al levantar entorno '{env_key}'. Código: {proc.returncode}")
|
fail(f"Fallo al levantar entorno '{env_key}'. Código: {proc.returncode}")
|
||||||
|
|
||||||
def bring_down(env_key=None):
|
def bring_down(env_key):
|
||||||
"""
|
env_path = env_file_path(env_key)
|
||||||
Intenta apagar usando el env proporcionado si existe el .env.
|
pname = PROJECT_NAMES.get(env_key)
|
||||||
Si no se pasa env_key o no existe el .env, hace un down genérico.
|
cmd = compose_cmd(["down"], env_key=env_key, env_file=env_path, project_name=pname)
|
||||||
"""
|
|
||||||
env_path = env_file_path(env_key) if env_key else None
|
|
||||||
cmd = compose_cmd(["down"], env_file=env_path)
|
|
||||||
info("Ejecutando: " + " ".join(cmd))
|
info("Ejecutando: " + " ".join(cmd))
|
||||||
proc = run(cmd)
|
proc = run(cmd)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
ok("Contenedores detenidos y red/volúmenes del proyecto desmontados (según corresponda).")
|
ok(f"Entorno '{env_key}' detenido correctamente.")
|
||||||
else:
|
else:
|
||||||
fail(f"Fallo al detener el entorno. Código: {proc.returncode}")
|
fail(f"Fallo al detener entorno '{env_key}'. Código: {proc.returncode}")
|
||||||
|
|
||||||
|
def running_ids(env_key):
|
||||||
|
env_path = env_file_path(env_key)
|
||||||
|
pname = PROJECT_NAMES.get(env_key)
|
||||||
|
cmd = compose_cmd(["ps", "--status", "running", "-q"], env_key=env_key, env_file=env_path, project_name=pname)
|
||||||
|
proc = run(cmd, capture_output=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
return []
|
||||||
|
return [l.strip() for l in proc.stdout.splitlines() if l.strip()]
|
||||||
|
|
||||||
|
# ---------- Acciones (globales) ----------
|
||||||
|
def bring_up_global(kind, force_recreate=False):
|
||||||
|
base_args = ["up", "-d"]
|
||||||
|
if force_recreate:
|
||||||
|
base_args.append("--force-recreate")
|
||||||
|
cmd = compose_cmd_global(base_args, kind=kind)
|
||||||
|
info("Ejecutando: " + " ".join(cmd))
|
||||||
|
proc = run(cmd)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
ok(f"Servicio global '{kind}' levantado correctamente.")
|
||||||
|
else:
|
||||||
|
fail(f"Fallo al levantar servicio global '{kind}'. Código: {proc.returncode}")
|
||||||
|
|
||||||
|
def bring_down_global(kind, remove=False):
|
||||||
|
"""
|
||||||
|
Apaga SOLO los servicios definidos en el compose global indicado.
|
||||||
|
- Primero 'stop <servicios>'.
|
||||||
|
- Opcionalmente, 'rm -f <servicios>' si remove=True.
|
||||||
|
"""
|
||||||
|
services = list_services_global(kind)
|
||||||
|
if not services:
|
||||||
|
warn(f"No se encontraron servicios en compose.{kind}.yaml.")
|
||||||
|
return
|
||||||
|
# stop específico
|
||||||
|
cmd = compose_cmd_global(["stop"] + services, kind=kind)
|
||||||
|
info("Ejecutando: " + " ".join(cmd))
|
||||||
|
proc = run(cmd)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
fail(f"Fallo al detener servicios de '{kind}'. Código: {proc.returncode}")
|
||||||
|
|
||||||
|
if remove:
|
||||||
|
cmd_rm = compose_cmd_global(["rm", "-f"] + services, kind=kind)
|
||||||
|
info("Ejecutando: " + " ".join(cmd_rm))
|
||||||
|
proc_rm = run(cmd_rm)
|
||||||
|
if proc_rm.returncode != 0:
|
||||||
|
fail(f"Fallo al remover servicios de '{kind}'. Código: {proc_rm.returncode}")
|
||||||
|
|
||||||
|
ok(f"Servicio(s) global(es) '{kind}' detenido(s).")
|
||||||
|
|
||||||
|
def running_ids_global(kind):
|
||||||
|
"""
|
||||||
|
Detecta si el compose global está corriendo revisando contenedores por servicio,
|
||||||
|
filtrando por labels (project+service).
|
||||||
|
"""
|
||||||
|
services = list_services_global(kind)
|
||||||
|
ids = []
|
||||||
|
for svc in services:
|
||||||
|
ids += docker_ps_by_labels(GLOBAL_PROJECT_NAME, service=svc, running_only=True)
|
||||||
|
# eliminar duplicados
|
||||||
|
return list(dict.fromkeys(ids))
|
||||||
|
|
||||||
|
# ---------- Estado y flujo ----------
|
||||||
|
def detect_status_summary():
|
||||||
|
dev_running = running_ids("development")
|
||||||
|
prod_running = running_ids("production")
|
||||||
|
npm_running = running_ids_global("npm")
|
||||||
|
dbeaver_running = running_ids_global("dbeaver")
|
||||||
|
|
||||||
|
print_header("Estado actual")
|
||||||
|
info(f"DESARROLLO: {len(dev_running)} contenedor(es) en ejecución.")
|
||||||
|
info(f"PRODUCCIÓN: {len(prod_running)} contenedor(es) en ejecución.")
|
||||||
|
info(f"NPM (global): {len(npm_running)} contenedor(es) en ejecución.")
|
||||||
|
info(f"DBEAVER (global): {len(dbeaver_running)} contenedor(es) en ejecución.\n")
|
||||||
|
|
||||||
|
return bool(dev_running), bool(prod_running), bool(npm_running), bool(dbeaver_running)
|
||||||
|
|
||||||
|
def detect_and_optionally_shutdown():
|
||||||
|
"""Muestra estado y ofrece (opcional) apagar dev/prod.
|
||||||
|
Los servicios globales se gestionan desde el menú principal (levantar/apagar).
|
||||||
|
"""
|
||||||
|
dev_on, prod_on, _npm_on, _dbeaver_on = detect_status_summary()
|
||||||
|
|
||||||
|
options = []
|
||||||
|
if dev_on:
|
||||||
|
options.append(("1", "Apagar entorno de DESARROLLO", "development"))
|
||||||
|
if prod_on:
|
||||||
|
options.append(("2", "Apagar entorno de PRODUCCIÓN", "production"))
|
||||||
|
options.append(("3", "Continuar sin detener nada", None))
|
||||||
|
|
||||||
|
if len(options) == 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Selecciona una opción:")
|
||||||
|
for opt in options:
|
||||||
|
key, label = opt[0], opt[1]
|
||||||
|
print(f" {key}) {label}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input("> ").strip()
|
||||||
|
selected = next((opt for opt in options if opt[0] == choice), None)
|
||||||
|
if not selected:
|
||||||
|
print("Opción inválida.")
|
||||||
|
continue
|
||||||
|
if choice == "3" or selected[2] is None:
|
||||||
|
ok("Continuamos sin detener nada.")
|
||||||
|
return
|
||||||
|
env_key = selected[2]
|
||||||
|
bring_down(env_key)
|
||||||
|
return
|
||||||
|
|
||||||
def main_menu():
|
def main_menu():
|
||||||
|
# Consultar estado de globales para decidir si mostrar opciones de "Levantar" o "Apagar"
|
||||||
|
_dev_on, _prod_on, npm_on, dbeaver_on = detect_status_summary()
|
||||||
|
|
||||||
print_header("Gestor de entornos Docker Compose")
|
print_header("Gestor de entornos Docker Compose")
|
||||||
print("Selecciona una opción:")
|
print("Selecciona una opción:")
|
||||||
print(" 1) Levantar entorno de DESARROLLO")
|
print(" 1) Levantar entorno de DESARROLLO")
|
||||||
print(" 2) Levantar entorno de PRODUCCIÓN")
|
print(" 2) Levantar entorno de PRODUCCIÓN")
|
||||||
print(" 3) Salir")
|
|
||||||
|
dynamic_keys = {}
|
||||||
|
next_key = 3
|
||||||
|
|
||||||
|
# NPM: opción según estado
|
||||||
|
if not npm_on:
|
||||||
|
print(f" {next_key}) Levantar NPM (compose.npm.yaml)")
|
||||||
|
dynamic_keys[str(next_key)] = ("global_up", "npm")
|
||||||
|
else:
|
||||||
|
print(f" {next_key}) Apagar NPM (compose.npm.yaml)")
|
||||||
|
dynamic_keys[str(next_key)] = ("global_down", "npm")
|
||||||
|
next_key += 1
|
||||||
|
|
||||||
|
# DBEAVER: opción según estado
|
||||||
|
if not dbeaver_on:
|
||||||
|
print(f" {next_key}) Levantar DBEAVER (compose.dbeaver.yaml)")
|
||||||
|
dynamic_keys[str(next_key)] = ("global_up", "dbeaver")
|
||||||
|
else:
|
||||||
|
print(f" {next_key}) Apagar DBEAVER (compose.dbeaver.yaml)")
|
||||||
|
dynamic_keys[str(next_key)] = ("global_down", "dbeaver")
|
||||||
|
next_key += 1
|
||||||
|
|
||||||
|
# Salir
|
||||||
|
print(f" {next_key}) Salir")
|
||||||
|
exit_key = str(next_key)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
choice = input("> ").strip()
|
choice = input("> ").strip()
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
bring_up("development") # incluye override
|
force = yes_no("¿Usar --force-recreate para DESARROLLO?", default="n")
|
||||||
|
bring_up("development", force_recreate=force)
|
||||||
return
|
return
|
||||||
elif choice == "2":
|
elif choice == "2":
|
||||||
bring_up("production", include_override=False) # sin override
|
force = yes_no("¿Usar --force-recreate para PRODUCCIÓN?", default="n")
|
||||||
|
bring_up("production", force_recreate=force)
|
||||||
return
|
return
|
||||||
elif choice == "3":
|
elif choice in dynamic_keys:
|
||||||
|
action, kind = dynamic_keys[choice]
|
||||||
|
if action == "global_up":
|
||||||
|
force = yes_no(f"¿Usar --force-recreate para {kind.upper()}?", default="n")
|
||||||
|
bring_up_global(kind, force_recreate=force)
|
||||||
|
return
|
||||||
|
elif action == "global_down":
|
||||||
|
remove = yes_no(f"¿También remover contenedores de {kind.upper()}? (rm -f)", default="n")
|
||||||
|
bring_down_global(kind, remove=remove)
|
||||||
|
return
|
||||||
|
elif choice == exit_key:
|
||||||
ok("Saliendo.")
|
ok("Saliendo.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
print("Opción inválida. Elige 1, 2 o 3.")
|
print("Opción inválida. Elige una de las opciones listadas.")
|
||||||
|
|
||||||
def detect_and_offer_shutdown():
|
|
||||||
# Paths de env (si existen)
|
|
||||||
dev_env = env_file_path("development")
|
|
||||||
prod_env = env_file_path("production")
|
|
||||||
|
|
||||||
# Helper: obtiene IDs running para una combinación de files/env
|
|
||||||
def running_ids(env_path, include_override):
|
|
||||||
cmd = compose_cmd(["ps", "--status", "running", "-q"],
|
|
||||||
env_file=env_path,
|
|
||||||
include_override=include_override)
|
|
||||||
proc = run(cmd, capture_output=True)
|
|
||||||
if proc.returncode != 0:
|
|
||||||
return []
|
|
||||||
return [l.strip() for l in proc.stdout.splitlines() if l.strip()]
|
|
||||||
|
|
||||||
# Dev usa override; Prod no
|
|
||||||
dev_running = running_ids(dev_env, include_override=True)
|
|
||||||
prod_running = running_ids(prod_env, include_override=False)
|
|
||||||
|
|
||||||
any_running = bool(dev_running or prod_running)
|
|
||||||
if any_running:
|
|
||||||
print_header("Contenedores activos detectados")
|
|
||||||
info(f"DESARROLLO: {len(dev_running)} contenedor(es) en ejecución.")
|
|
||||||
info(f"PRODUCCIÓN: {len(prod_running)} contenedor(es) en ejecución.\n")
|
|
||||||
|
|
||||||
options = []
|
|
||||||
if dev_running:
|
|
||||||
options.append(("1", "Apagar entorno de DESARROLLO", ("development", True)))
|
|
||||||
if prod_running:
|
|
||||||
options.append(("2", "Apagar entorno de PRODUCCIÓN", ("production", False)))
|
|
||||||
options.append(("3", "Mantener todo como está y salir", None))
|
|
||||||
|
|
||||||
print("Selecciona una opción:")
|
|
||||||
for key, label, _ in options:
|
|
||||||
print(f" {key}) {label}")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
choice = input("> ").strip()
|
|
||||||
selected = next((opt for opt in options if opt[0] == choice), None)
|
|
||||||
if not selected:
|
|
||||||
print("Opción inválida.")
|
|
||||||
continue
|
|
||||||
if choice == "3":
|
|
||||||
ok("Se mantiene el estado actual.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
env_key, _include_override = selected[2]
|
|
||||||
info(f"Intentando apagar entorno de {env_key.upper()}…")
|
|
||||||
bring_down(env_key) # ya respeta --env-file y el include_override de prod no usa override
|
|
||||||
ok("Listo.")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
info("No hay contenedores activos del proyecto.")
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
check_prereqs()
|
check_prereqs()
|
||||||
detect_and_offer_shutdown()
|
# Mostrar estado y permitir opcionalmente apagar dev/prod
|
||||||
|
detect_and_optionally_shutdown()
|
||||||
|
# Menú de gestión (incluye globales: levantar o apagar según estado)
|
||||||
main_menu()
|
main_menu()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n")
|
print("\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user