@batistafull/deploy-server 2.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -19
- package/lib/deploy.js +94 -35
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @batistafull/deploy-server
|
|
2
2
|
|
|
3
|
-
Herramienta CLI de despliegue dirigida por configuración. Ejecuta los comandos de build de tu proyecto y copia las carpetas
|
|
3
|
+
Herramienta CLI de despliegue dirigida por configuración. Ejecuta los comandos de build de tu proyecto y copia las carpetas, archivos o globs que indiques a la carpeta destino, con opción de subir el resultado por FTP/FTPS.
|
|
4
4
|
|
|
5
5
|
## Instalación
|
|
6
6
|
|
|
@@ -52,13 +52,13 @@ Crea `deploy.config.json` en la raíz de tu proyecto. **Este archivo lo creas t
|
|
|
52
52
|
```json
|
|
53
53
|
{
|
|
54
54
|
"output": "C:/inetpub/app.host",
|
|
55
|
-
"
|
|
56
|
-
"server": "api",
|
|
55
|
+
"sources": ["dist/", "api/", "robots.txt"],
|
|
57
56
|
"buildCommand": ["npm install", "npm run build"],
|
|
58
|
-
"exclude": ["
|
|
57
|
+
"exclude": ["dist/.htaccess", "api/node_modules"],
|
|
59
58
|
"ftp": {
|
|
60
59
|
"enabled": false,
|
|
61
60
|
"host": "ftp.example.com",
|
|
61
|
+
"port": 21,
|
|
62
62
|
"secure": true,
|
|
63
63
|
"remotePath": "/public_html"
|
|
64
64
|
}
|
|
@@ -67,26 +67,47 @@ Crea `deploy.config.json` en la raíz de tu proyecto. **Este archivo lo creas t
|
|
|
67
67
|
|
|
68
68
|
### Campos
|
|
69
69
|
|
|
70
|
-
| Campo | Tipo | Descripción
|
|
71
|
-
| -------------- | --------------------- |
|
|
72
|
-
| `output` | `string` | Ruta destino
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `ftp` | `object` | (Opcional) Configuración de subida por FTP/FTPS. Ver abajo. |
|
|
70
|
+
| Campo | Tipo | Descripción |
|
|
71
|
+
| -------------- | --------------------- | --------------------------------------------------------------- |
|
|
72
|
+
| `output` | `string` | Ruta destino local (ver nota). Se ignora si el FTP está activo. |
|
|
73
|
+
| `sources` | `string[]` | Carpetas, archivos o globs a copiar (ver abajo). |
|
|
74
|
+
| `buildCommand` | `string` o `string[]` | Comando(s) de build. Si es un array, se ejecutan en orden. |
|
|
75
|
+
| `exclude` | `string[]` | Rutas a excluir, relativas al proyecto (ver abajo). |
|
|
76
|
+
| `ftp` | `object` | (Opcional) Configuración de subida por FTP/FTPS. Ver abajo. |
|
|
78
77
|
|
|
79
78
|
> **`output`:** si es una ruta **absoluta** se usa tal cual (ej: `C:/inetpub/app.host`);
|
|
80
79
|
> si es **relativa** se resuelve desde la raíz del proyecto (ej: `../deploy/app.host`).
|
|
80
|
+
> **Con FTP activo (`ftp.enabled: true`) no se deja copia local:** los archivos se
|
|
81
|
+
> preparan en una carpeta temporal, se suben y se borra. En ese caso `output` no es necesario.
|
|
82
|
+
|
|
83
|
+
### `sources` — qué se copia
|
|
84
|
+
|
|
85
|
+
Cada entrada es una carpeta, un archivo o un glob, relativo a la raíz del proyecto:
|
|
86
|
+
|
|
87
|
+
| Entrada | Significado |
|
|
88
|
+
| ------------ | -------------------------------------------------------------------------------- |
|
|
89
|
+
| `dist/` | Carpeta completa (recursivo). Su contenido se copia a la raíz del output. |
|
|
90
|
+
| `dist/*.css` | Glob: solo los `.css` del primer nivel de `dist`, copiados a la raíz del output. |
|
|
91
|
+
| `robots.txt` | Un archivo suelto, copiado por su nombre a la raíz del output. |
|
|
92
|
+
|
|
93
|
+
En todos los casos el contenido se copia a la **raíz del output** (se quita la carpeta base). Las carpetas se indican con `/` al final.
|
|
94
|
+
|
|
95
|
+
### `exclude` — qué se omite
|
|
96
|
+
|
|
97
|
+
Cada entrada es una ruta **relativa al proyecto** (no un nombre suelto), scopeada a la carpeta de origen:
|
|
98
|
+
|
|
99
|
+
- `"dist/.htaccess"` → omite ese archivo concreto dentro de `dist`.
|
|
100
|
+
- `"api/node_modules"` → omite esa carpeta entera (y su contenido) dentro de `api`.
|
|
81
101
|
|
|
82
102
|
### FTP / FTPS
|
|
83
103
|
|
|
84
|
-
La subida por FTP está desactivada salvo que pongas `ftp.enabled: true`.
|
|
104
|
+
La subida por FTP está desactivada salvo que pongas `ftp.enabled: true`. **Cuando está activa, `output` se ignora** (no se genera copia local: se usa una carpeta temporal que se borra tras subir).
|
|
85
105
|
|
|
86
106
|
| Campo | Descripción |
|
|
87
107
|
| ---------------- | -------------------------------------------------------------------------- |
|
|
88
|
-
| `ftp.enabled` | `true` para activar la subida.
|
|
108
|
+
| `ftp.enabled` | `true` para activar la subida. Si está activo, `output` se ignora. |
|
|
89
109
|
| `ftp.host` | Host del servidor FTP (fallback de `FTP_HOST`). |
|
|
110
|
+
| `ftp.port` | Puerto (fallback de `FTP_PORT`). Por defecto `21`. |
|
|
90
111
|
| `ftp.secure` | `true` (por defecto) usa FTPS con TLS. Pon `false` solo si no hay soporte. |
|
|
91
112
|
| `ftp.remotePath` | Carpeta remota destino. |
|
|
92
113
|
|
|
@@ -109,11 +130,11 @@ Si el FTP está habilitado y faltan `FTP_HOST`, `FTP_USER` o `FTP_PASSWORD` (y t
|
|
|
109
130
|
## Qué hace, paso a paso
|
|
110
131
|
|
|
111
132
|
1. Carga el `deploy.config.json`.
|
|
112
|
-
2.
|
|
113
|
-
3.
|
|
114
|
-
4.
|
|
115
|
-
5. Copia `
|
|
116
|
-
6. Si `ftp.enabled`, sube
|
|
133
|
+
2. Determina el destino: la carpeta `output`, o una temporal si el FTP está activo.
|
|
134
|
+
3. Si se pasa `--clean` (y hay `output` local), borra la carpeta de salida.
|
|
135
|
+
4. Ejecuta el/los `buildCommand`.
|
|
136
|
+
5. Copia cada entrada de `sources` → destino (aplicando `exclude`).
|
|
137
|
+
6. Si `ftp.enabled`, sube los archivos por FTP/FTPS y borra la carpeta temporal.
|
|
117
138
|
|
|
118
139
|
## Licencia
|
|
119
140
|
|
package/lib/deploy.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const os = require("os");
|
|
2
3
|
const fs = require("fs-extra");
|
|
3
4
|
const chalk = require("chalk");
|
|
4
5
|
const { execSync } = require("child_process");
|
|
5
6
|
const ftp = require("basic-ftp");
|
|
7
|
+
const fg = require("fast-glob");
|
|
6
8
|
|
|
7
9
|
// Ejecuta una lista de comandos de build en orden
|
|
8
10
|
function runBuildCommands(commands, cwd) {
|
|
@@ -12,49 +14,106 @@ function runBuildCommands(commands, cwd) {
|
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Dado un patrón (con "/"), calcula el patrón glob y la carpeta base a "quitar"
|
|
18
|
+
// al copiar hacia el output.
|
|
19
|
+
// "dist/" -> { glob: "dist/**/*", base: "dist" } (carpeta completa)
|
|
20
|
+
// "dist/*.css" -> { glob: "dist/*.css", base: "dist" } (glob dentro de dist)
|
|
21
|
+
// "dist/app.js" -> { glob: "dist/app.js", base: "dist" } (archivo suelto)
|
|
22
|
+
function resolveSource(raw) {
|
|
23
|
+
const source = raw.replace(/\\/g, "/");
|
|
24
|
+
const hasGlob = (s) => /[*?[\]{}!()]/.test(s);
|
|
25
|
+
|
|
26
|
+
if (source.endsWith("/")) {
|
|
27
|
+
const dir = source.replace(/\/+$/, "");
|
|
28
|
+
return { glob: `${dir}/**/*`, base: dir };
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(pattern) => name === pattern || relative.split(path.sep).includes(pattern)
|
|
29
|
-
);
|
|
31
|
+
if (hasGlob(source)) {
|
|
32
|
+
// base = prefijo estático anterior al primer segmento con comodines
|
|
33
|
+
const baseSegs = [];
|
|
34
|
+
for (const seg of source.split("/")) {
|
|
35
|
+
if (hasGlob(seg)) break;
|
|
36
|
+
baseSegs.push(seg);
|
|
30
37
|
}
|
|
38
|
+
return { glob: source, base: baseSegs.join("/") || "." };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// archivo (o carpeta sin "/") suelto: se copia bajo su nombre
|
|
42
|
+
return { glob: source, base: path.posix.dirname(source) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Copia cada 'source' al output. Los 'exclude' son rutas relativas al proyecto
|
|
46
|
+
// (ej: "dist/.htaccess") y se aplican como patrones ignorados.
|
|
47
|
+
async function copySources(sources, outputPath, exclude, projectRoot) {
|
|
48
|
+
// Para excluir una carpeta entera basta con nombrarla; añadimos "/**" para
|
|
49
|
+
// cubrir también su contenido.
|
|
50
|
+
const ignore = exclude.flatMap((e) => {
|
|
51
|
+
const p = e.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
52
|
+
return [p, `${p}/**`];
|
|
31
53
|
});
|
|
54
|
+
|
|
55
|
+
for (const raw of sources) {
|
|
56
|
+
const { glob, base } = resolveSource(raw);
|
|
57
|
+
|
|
58
|
+
const matches = await fg(glob, {
|
|
59
|
+
cwd: projectRoot,
|
|
60
|
+
dot: true,
|
|
61
|
+
onlyFiles: true,
|
|
62
|
+
ignore
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (matches.length === 0) {
|
|
66
|
+
console.log(chalk.yellow(`⚠️ Sin coincidencias, se omite: ${raw}`));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(chalk.blue(`📦 Copiando ${raw} (${matches.length} archivo(s))...`));
|
|
71
|
+
for (const rel of matches) {
|
|
72
|
+
const from = path.join(projectRoot, rel);
|
|
73
|
+
const dest = path.join(outputPath, path.relative(base, rel));
|
|
74
|
+
await fs.ensureDir(path.dirname(dest));
|
|
75
|
+
await fs.copy(from, dest);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
32
78
|
}
|
|
33
79
|
|
|
34
80
|
module.exports = async function deploy(options) {
|
|
35
81
|
const config = require(path.resolve(process.cwd(), options.config));
|
|
36
82
|
|
|
37
83
|
const projectRoot = process.cwd();
|
|
84
|
+
const ftpEnabled = !!config.ftp?.enabled;
|
|
38
85
|
|
|
39
|
-
if (!config.
|
|
40
|
-
throw new Error(
|
|
86
|
+
if (!Array.isArray(config.sources) || config.sources.length === 0) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Falta 'sources' en el config: indica un array de carpetas/archivos a copiar (ej: [\"dist/\", \"api/\"])."
|
|
89
|
+
);
|
|
41
90
|
}
|
|
42
91
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
92
|
+
// Con FTP no se deja copia local: se usa una carpeta temporal solo para
|
|
93
|
+
// preparar la subida y se borra al terminar. Sin FTP se requiere 'output'.
|
|
94
|
+
let outputPath;
|
|
95
|
+
let staging = false;
|
|
46
96
|
|
|
47
|
-
|
|
48
|
-
|
|
97
|
+
if (ftpEnabled) {
|
|
98
|
+
outputPath = await fs.mkdtemp(path.join(os.tmpdir(), "deploy-server-"));
|
|
99
|
+
staging = true;
|
|
100
|
+
} else {
|
|
101
|
+
if (!config.output) {
|
|
102
|
+
throw new Error("Falta 'output' en el config: indica la carpeta destino del deploy.");
|
|
103
|
+
}
|
|
104
|
+
// 'output' es la ruta destino. Si es absoluta se usa tal cual;
|
|
105
|
+
// si es relativa se resuelve desde la raíz del proyecto.
|
|
106
|
+
outputPath = path.resolve(projectRoot, config.output);
|
|
107
|
+
|
|
108
|
+
// Clean
|
|
109
|
+
if (options.clean) {
|
|
110
|
+
console.log(chalk.gray("🧹 Cleaning output..."));
|
|
111
|
+
await fs.remove(outputPath);
|
|
112
|
+
}
|
|
49
113
|
|
|
50
|
-
|
|
51
|
-
if (options.clean) {
|
|
52
|
-
console.log(chalk.gray("🧹 Cleaning output..."));
|
|
53
|
-
await fs.remove(outputPath);
|
|
114
|
+
await fs.ensureDir(outputPath);
|
|
54
115
|
}
|
|
55
116
|
|
|
56
|
-
await fs.ensureDir(outputPath);
|
|
57
|
-
|
|
58
117
|
// BUILD (ARRAY)
|
|
59
118
|
if (Array.isArray(config.buildCommand)) {
|
|
60
119
|
runBuildCommands(config.buildCommand, projectRoot);
|
|
@@ -65,16 +124,11 @@ module.exports = async function deploy(options) {
|
|
|
65
124
|
});
|
|
66
125
|
}
|
|
67
126
|
|
|
68
|
-
// COPY
|
|
69
|
-
|
|
70
|
-
await copyFiltered(distPath, outputPath, config.exclude || []);
|
|
71
|
-
|
|
72
|
-
// COPY SERVER
|
|
73
|
-
console.log(chalk.blue("📦 Copying server..."));
|
|
74
|
-
await copyFiltered(serverPath, outputPath, config.exclude || []);
|
|
127
|
+
// COPY SOURCES
|
|
128
|
+
await copySources(config.sources, outputPath, config.exclude || [], projectRoot);
|
|
75
129
|
|
|
76
130
|
// FTP (opcional)
|
|
77
|
-
if (
|
|
131
|
+
if (ftpEnabled) {
|
|
78
132
|
const client = new ftp.Client();
|
|
79
133
|
|
|
80
134
|
// Las credenciales se leen preferentemente de variables de entorno.
|
|
@@ -82,6 +136,7 @@ module.exports = async function deploy(options) {
|
|
|
82
136
|
const host = process.env.FTP_HOST || config.ftp.host;
|
|
83
137
|
const user = process.env.FTP_USER || config.ftp.user;
|
|
84
138
|
const password = process.env.FTP_PASSWORD || config.ftp.password;
|
|
139
|
+
const port = Number(process.env.FTP_PORT || config.ftp.port) || undefined;
|
|
85
140
|
// FTPS por defecto; poner ftp.secure=false en el config solo si el
|
|
86
141
|
// servidor no soporta TLS (no recomendado).
|
|
87
142
|
const secure = config.ftp.secure !== false;
|
|
@@ -95,7 +150,7 @@ module.exports = async function deploy(options) {
|
|
|
95
150
|
console.log(chalk.yellow(`📡 Connecting FTP (${secure ? "FTPS" : "sin cifrar"})...`));
|
|
96
151
|
|
|
97
152
|
try {
|
|
98
|
-
await client.access({ host, user, password, secure });
|
|
153
|
+
await client.access({ host, port, user, password, secure });
|
|
99
154
|
|
|
100
155
|
await client.ensureDir(config.ftp.remotePath);
|
|
101
156
|
await client.uploadFromDir(outputPath);
|
|
@@ -103,6 +158,10 @@ module.exports = async function deploy(options) {
|
|
|
103
158
|
console.log(chalk.green("🌐 FTP upload done"));
|
|
104
159
|
} finally {
|
|
105
160
|
client.close();
|
|
161
|
+
// Borra la carpeta temporal de preparación (no se deja copia local).
|
|
162
|
+
if (staging) {
|
|
163
|
+
await fs.remove(outputPath);
|
|
164
|
+
}
|
|
106
165
|
}
|
|
107
166
|
}
|
|
108
167
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@batistafull/deploy-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Config-driven deploy tool that builds and copies folders, files or globs to a target path, with optional FTP/FTPS upload",
|
|
5
5
|
"main": "bin/deploy-server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"deploy-server": "bin/deploy-server.js"
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"lib/"
|
|
12
12
|
],
|
|
13
|
-
"keywords": [
|
|
13
|
+
"keywords": [
|
|
14
|
+
"deploy",
|
|
15
|
+
"cli",
|
|
16
|
+
"nodejs"
|
|
17
|
+
],
|
|
14
18
|
"author": "batistafull",
|
|
15
19
|
"license": "MIT",
|
|
16
20
|
"type": "commonjs",
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
"basic-ftp": "^5.0.5",
|
|
22
26
|
"chalk": "^4.1.2",
|
|
23
27
|
"commander": "^12.1.0",
|
|
28
|
+
"fast-glob": "^3.3.3",
|
|
24
29
|
"fs-extra": "^11.2.0"
|
|
25
30
|
}
|
|
26
|
-
}
|
|
31
|
+
}
|