@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.
Files changed (3) hide show
  1. package/README.md +40 -19
  2. package/lib/deploy.js +94 -35
  3. 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 `dist` y `server` a la carpeta destino que indiques, con opción de subir el resultado por FTP/FTPS.
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
- "dist": "dist",
56
- "server": "api",
55
+ "sources": ["dist/", "api/", "robots.txt"],
57
56
  "buildCommand": ["npm install", "npm run build"],
58
- "exclude": ["node_modules", ".git", ".env"],
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 del deploy (ver nota abajo). |
73
- | `dist` | `string` | Ruta (relativa al proyecto) de la carpeta compilada a copiar. |
74
- | `server` | `string` | Ruta (relativa al proyecto) de la carpeta del servidor/API. |
75
- | `buildCommand` | `string` o `string[]` | Comando(s) de build. Si es un array, se ejecutan en orden. |
76
- | `exclude` | `string[]` | Nombres de archivos o carpetas a excluir al copiar. |
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. Si se pasa `--clean`, borra la carpeta de salida.
113
- 3. Ejecuta el/los `buildCommand`.
114
- 4. Copia `dist` → `<output>` (aplicando `exclude`).
115
- 5. Copia `server` → `<output>` (aplicando `exclude`).
116
- 6. Si `ftp.enabled`, sube la carpeta de salida por FTP/FTPS.
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
- // Copia un directorio excluyendo los patrones indicados (match por nombre o ruta relativa)
16
- async function copyFiltered(srcPath, destPath, exclude = []) {
17
- if (!(await fs.pathExists(srcPath))) {
18
- console.log(chalk.yellow(`⚠️ Origen no encontrado, se omite: ${srcPath}`));
19
- return;
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
- await fs.copy(srcPath, destPath, {
23
- filter: (src) => {
24
- const relative = path.relative(srcPath, src);
25
- if (!relative) return true; // el propio directorio raíz
26
- const name = path.basename(src);
27
- return !exclude.some(
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.output) {
40
- throw new Error("Falta 'output' en el config: indica la carpeta destino del deploy.");
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
- // 'output' es la ruta destino. Si es absoluta se usa tal cual;
44
- // si es relativa se resuelve desde la raíz del proyecto.
45
- const outputPath = path.resolve(projectRoot, config.output);
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
- const distPath = path.join(projectRoot, config.dist);
48
- const serverPath = path.join(projectRoot, config.server);
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
- // Clean
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 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 || []);
127
+ // COPY SOURCES
128
+ await copySources(config.sources, outputPath, config.exclude || [], projectRoot);
75
129
 
76
130
  // FTP (opcional)
77
- if (config.ftp?.enabled) {
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": "2.0.0",
4
- "description": "Deploy tool that builds and copies dist + api to Documents/app.host",
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": ["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
+ }