@batistafull/deploy-server 1.1.0 → 3.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.
Files changed (3) hide show
  1. package/README.md +136 -0
  2. package/lib/deploy.js +71 -28
  3. package/package.json +9 -4
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @batistafull/deploy-server
2
+
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
+
5
+ ## Instalación
6
+
7
+ Global:
8
+
9
+ ```bash
10
+ npm install -g @batistafull/deploy-server
11
+ ```
12
+
13
+ O como dependencia de desarrollo del proyecto:
14
+
15
+ ```bash
16
+ npm install --save-dev @batistafull/deploy-server
17
+ ```
18
+
19
+ ## Uso
20
+
21
+ Desde la raíz de tu proyecto, crea un archivo `deploy.config.json` (ver formato abajo) y ejecuta:
22
+
23
+ ```bash
24
+ deploy-server
25
+ ```
26
+
27
+ ### Opciones
28
+
29
+ | Opción | Descripción | Por defecto |
30
+ | ----------------- | ---------------------------------------------- | -------------------- |
31
+ | `--config <path>` | Ruta al archivo de configuración | `deploy.config.json` |
32
+ | `--clean` | Limpia la carpeta de salida antes de desplegar | (desactivado) |
33
+ | `-h, --help` | Muestra la ayuda | |
34
+
35
+ Ejemplos:
36
+
37
+ ```bash
38
+ # Deploy con la config por defecto
39
+ deploy-server
40
+
41
+ # Limpiar la salida antes de copiar
42
+ deploy-server --clean
43
+
44
+ # Usar otro archivo de config
45
+ deploy-server --config configs/prod.json
46
+ ```
47
+
48
+ ## Configuración
49
+
50
+ Crea `deploy.config.json` en la raíz de tu proyecto. **Este archivo lo creas tú**, no viene incluido en el paquete.
51
+
52
+ ```json
53
+ {
54
+ "output": "C:/inetpub/app.host",
55
+ "sources": ["dist/", "api/", "robots.txt"],
56
+ "buildCommand": ["npm install", "npm run build"],
57
+ "exclude": ["dist/.htaccess", "api/node_modules"],
58
+ "ftp": {
59
+ "enabled": false,
60
+ "host": "ftp.example.com",
61
+ "secure": true,
62
+ "remotePath": "/public_html"
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Campos
68
+
69
+ | Campo | Tipo | Descripción |
70
+ | -------------- | --------------------- | ----------------------------------------------------------- |
71
+ | `output` | `string` | Ruta destino del deploy (ver nota abajo). |
72
+ | `sources` | `string[]` | Carpetas, archivos o globs a copiar (ver abajo). |
73
+ | `buildCommand` | `string` o `string[]` | Comando(s) de build. Si es un array, se ejecutan en orden. |
74
+ | `exclude` | `string[]` | Rutas a excluir, relativas al proyecto (ver abajo). |
75
+ | `ftp` | `object` | (Opcional) Configuración de subida por FTP/FTPS. Ver abajo. |
76
+
77
+ > **`output`:** si es una ruta **absoluta** se usa tal cual (ej: `C:/inetpub/app.host`);
78
+ > si es **relativa** se resuelve desde la raíz del proyecto (ej: `../deploy/app.host`).
79
+
80
+ ### `sources` — qué se copia
81
+
82
+ Cada entrada es una carpeta, un archivo o un glob, relativo a la raíz del proyecto:
83
+
84
+ | Entrada | Significado |
85
+ | ------------ | -------------------------------------------------------------------------------- |
86
+ | `dist/` | Carpeta completa (recursivo). Su contenido se copia a la raíz del output. |
87
+ | `dist/*.css` | Glob: solo los `.css` del primer nivel de `dist`, copiados a la raíz del output. |
88
+ | `robots.txt` | Un archivo suelto, copiado por su nombre a la raíz del output. |
89
+
90
+ 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.
91
+
92
+ ### `exclude` — qué se omite
93
+
94
+ Cada entrada es una ruta **relativa al proyecto** (no un nombre suelto), scopeada a la carpeta de origen:
95
+
96
+ - `"dist/.htaccess"` → omite ese archivo concreto dentro de `dist`.
97
+ - `"api/node_modules"` → omite esa carpeta entera (y su contenido) dentro de `api`.
98
+
99
+ ### FTP / FTPS
100
+
101
+ La subida por FTP está desactivada salvo que pongas `ftp.enabled: true`.
102
+
103
+ | Campo | Descripción |
104
+ | ---------------- | -------------------------------------------------------------------------- |
105
+ | `ftp.enabled` | `true` para activar la subida. |
106
+ | `ftp.host` | Host del servidor FTP (fallback de `FTP_HOST`). |
107
+ | `ftp.secure` | `true` (por defecto) usa FTPS con TLS. Pon `false` solo si no hay soporte. |
108
+ | `ftp.remotePath` | Carpeta remota destino. |
109
+
110
+ > **Seguridad:** las credenciales se leen preferentemente de variables de entorno.
111
+ > **No pongas la contraseña en el JSON.**
112
+
113
+ ```bash
114
+ # Linux / macOS
115
+ FTP_HOST=ftp.tuservidor.com FTP_USER=usuario FTP_PASSWORD=secreto deploy-server
116
+ ```
117
+
118
+ ```powershell
119
+ # Windows (PowerShell)
120
+ $env:FTP_HOST="ftp.tuservidor.com"; $env:FTP_USER="usuario"; $env:FTP_PASSWORD="secreto"
121
+ deploy-server
122
+ ```
123
+
124
+ Si el FTP está habilitado y faltan `FTP_HOST`, `FTP_USER` o `FTP_PASSWORD` (y tampoco están en el config), el deploy falla con un error claro.
125
+
126
+ ## Qué hace, paso a paso
127
+
128
+ 1. Carga el `deploy.config.json`.
129
+ 2. Si se pasa `--clean`, borra la carpeta de salida.
130
+ 3. Ejecuta el/los `buildCommand`.
131
+ 4. Copia cada entrada de `sources` → `<output>` (aplicando `exclude`).
132
+ 5. Si `ftp.enabled`, sube la carpeta de salida por FTP/FTPS.
133
+
134
+ ## Licencia
135
+
136
+ MIT
package/lib/deploy.js CHANGED
@@ -1,9 +1,9 @@
1
1
  const path = require("path");
2
- const os = require("os");
3
2
  const fs = require("fs-extra");
4
3
  const chalk = require("chalk");
5
4
  const { execSync } = require("child_process");
6
5
  const ftp = require("basic-ftp");
6
+ const fg = require("fast-glob");
7
7
 
8
8
  // Ejecuta una lista de comandos de build en orden
9
9
  function runBuildCommands(commands, cwd) {
@@ -13,23 +13,67 @@ function runBuildCommands(commands, cwd) {
13
13
  }
14
14
  }
15
15
 
16
- // Copia un directorio excluyendo los patrones indicados (match por nombre o ruta relativa)
17
- async function copyFiltered(srcPath, destPath, exclude = []) {
18
- if (!(await fs.pathExists(srcPath))) {
19
- console.log(chalk.yellow(`⚠️ Origen no encontrado, se omite: ${srcPath}`));
20
- return;
16
+ // Dado un patrón (con "/"), calcula el patrón glob y la carpeta base a "quitar"
17
+ // al copiar hacia el output.
18
+ // "dist/" -> { glob: "dist/**/*", base: "dist" } (carpeta completa)
19
+ // "dist/*.css" -> { glob: "dist/*.css", base: "dist" } (glob dentro de dist)
20
+ // "dist/app.js" -> { glob: "dist/app.js", base: "dist" } (archivo suelto)
21
+ function resolveSource(raw) {
22
+ const source = raw.replace(/\\/g, "/");
23
+ const hasGlob = (s) => /[*?[\]{}!()]/.test(s);
24
+
25
+ if (source.endsWith("/")) {
26
+ const dir = source.replace(/\/+$/, "");
27
+ return { glob: `${dir}/**/*`, base: dir };
21
28
  }
22
29
 
23
- await fs.copy(srcPath, destPath, {
24
- filter: (src) => {
25
- const relative = path.relative(srcPath, src);
26
- if (!relative) return true; // el propio directorio raíz
27
- const name = path.basename(src);
28
- return !exclude.some(
29
- (pattern) => name === pattern || relative.split(path.sep).includes(pattern)
30
- );
30
+ if (hasGlob(source)) {
31
+ // base = prefijo estático anterior al primer segmento con comodines
32
+ const baseSegs = [];
33
+ for (const seg of source.split("/")) {
34
+ if (hasGlob(seg)) break;
35
+ baseSegs.push(seg);
31
36
  }
37
+ return { glob: source, base: baseSegs.join("/") || "." };
38
+ }
39
+
40
+ // archivo (o carpeta sin "/") suelto: se copia bajo su nombre
41
+ return { glob: source, base: path.posix.dirname(source) };
42
+ }
43
+
44
+ // Copia cada 'source' al output. Los 'exclude' son rutas relativas al proyecto
45
+ // (ej: "dist/.htaccess") y se aplican como patrones ignorados.
46
+ async function copySources(sources, outputPath, exclude, projectRoot) {
47
+ // Para excluir una carpeta entera basta con nombrarla; añadimos "/**" para
48
+ // cubrir también su contenido.
49
+ const ignore = exclude.flatMap((e) => {
50
+ const p = e.replace(/\\/g, "/").replace(/\/+$/, "");
51
+ return [p, `${p}/**`];
32
52
  });
53
+
54
+ for (const raw of sources) {
55
+ const { glob, base } = resolveSource(raw);
56
+
57
+ const matches = await fg(glob, {
58
+ cwd: projectRoot,
59
+ dot: true,
60
+ onlyFiles: true,
61
+ ignore
62
+ });
63
+
64
+ if (matches.length === 0) {
65
+ console.log(chalk.yellow(`⚠️ Sin coincidencias, se omite: ${raw}`));
66
+ continue;
67
+ }
68
+
69
+ console.log(chalk.blue(`📦 Copiando ${raw} (${matches.length} archivo(s))...`));
70
+ for (const rel of matches) {
71
+ const from = path.join(projectRoot, rel);
72
+ const dest = path.join(outputPath, path.relative(base, rel));
73
+ await fs.ensureDir(path.dirname(dest));
74
+ await fs.copy(from, dest);
75
+ }
76
+ }
33
77
  }
34
78
 
35
79
  module.exports = async function deploy(options) {
@@ -37,15 +81,19 @@ module.exports = async function deploy(options) {
37
81
 
38
82
  const projectRoot = process.cwd();
39
83
 
40
- const documentsPath =
41
- process.platform === "win32"
42
- ? path.join(process.env.USERPROFILE, "Documents")
43
- : path.join(os.homedir(), "Documents");
84
+ if (!config.output) {
85
+ throw new Error("Falta 'output' en el config: indica la carpeta destino del deploy.");
86
+ }
44
87
 
45
- const outputPath = path.join(documentsPath, config.output);
88
+ if (!Array.isArray(config.sources) || config.sources.length === 0) {
89
+ throw new Error(
90
+ "Falta 'sources' en el config: indica un array de carpetas/archivos a copiar (ej: [\"dist/\", \"api/\"])."
91
+ );
92
+ }
46
93
 
47
- const distPath = path.join(projectRoot, config.dist);
48
- const serverPath = path.join(projectRoot, config.server);
94
+ // 'output' es la ruta destino. Si es absoluta se usa tal cual;
95
+ // si es relativa se resuelve desde la raíz del proyecto.
96
+ const outputPath = path.resolve(projectRoot, config.output);
49
97
 
50
98
  // Clean
51
99
  if (options.clean) {
@@ -65,13 +113,8 @@ module.exports = async function deploy(options) {
65
113
  });
66
114
  }
67
115
 
68
- // COPY DIST
69
- console.log(chalk.blue("📦 Copying dist..."));
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 || []);
116
+ // COPY SOURCES
117
+ await copySources(config.sources, outputPath, config.exclude || [], projectRoot);
75
118
 
76
119
  // FTP (opcional)
77
120
  if (config.ftp?.enabled) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@batistafull/deploy-server",
3
- "version": "1.1.0",
4
- "description": "Deploy tool that builds and copies dist + api to Documents/app.host",
3
+ "version": "3.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": ["deploy", "cli", "nodejs"],
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
+ }