Dockers
This commit is contained in:
parent
d6c5d067cb
commit
cecba33ebb
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,7 +9,7 @@ dist-ssr
|
|||||||
.cache
|
.cache
|
||||||
server/dist
|
server/dist
|
||||||
public/dist
|
public/dist
|
||||||
|
out
|
||||||
|
|
||||||
apps/**/false/*
|
apps/**/false/*
|
||||||
false
|
false
|
||||||
|
|||||||
94
Dockerfile
Normal file
94
Dockerfile
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7-labs
|
||||||
|
|
||||||
|
ARG NODE_IMAGE=node:22-alpine
|
||||||
|
ARG PNPM_VERSION=10.20.0
|
||||||
|
|
||||||
|
########################
|
||||||
|
# 0) Base común
|
||||||
|
########################
|
||||||
|
FROM ${NODE_IMAGE} AS base
|
||||||
|
ENV CI=1 \
|
||||||
|
NODE_ENV=production
|
||||||
|
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
||||||
|
|
||||||
|
WORKDIR /repo
|
||||||
|
|
||||||
|
########################
|
||||||
|
# 1) Turborepo prune (reduce contexto)
|
||||||
|
########################
|
||||||
|
FROM base AS pruner
|
||||||
|
|
||||||
|
# Copiar archivos raíz necesarios
|
||||||
|
COPY turbo.json package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Copiar los directorios relevantes del monorepo
|
||||||
|
COPY packages ./packages
|
||||||
|
COPY apps ./apps
|
||||||
|
COPY modules ./modules
|
||||||
|
|
||||||
|
# Ejecuta prune para @erp/factuges-server
|
||||||
|
RUN npx --yes turbo@2.5.8 prune @erp/factuges-server --docker
|
||||||
|
|
||||||
|
########################
|
||||||
|
# 2) Instalar deps de la parte pruned (usando pnpm fetch para cache)
|
||||||
|
########################
|
||||||
|
#FROM base AS installer
|
||||||
|
|
||||||
|
# Traemos lockfile y pnpm-store desde la etapa prune
|
||||||
|
#COPY --from=pruner /repo/out/json/ ./
|
||||||
|
#COPY --from=pruner /repo/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||||
|
|
||||||
|
# Prefetch a store global (rápido y cacheable)
|
||||||
|
#RUN pnpm fetch
|
||||||
|
#RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
########################
|
||||||
|
# 3) Builder: código + link deps y build
|
||||||
|
########################
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
# Copiamos todo lo “pruned” (json, lock, full)
|
||||||
|
COPY --from=pruner /repo/out/full/ ./
|
||||||
|
COPY --from=pruner /repo/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||||
|
|
||||||
|
# Reutilizamos la store prefetch
|
||||||
|
#COPY --from=installer /root/.local/share/pnpm/store /root/.local/share/pnpm/store
|
||||||
|
|
||||||
|
# Instalamos solo lo necesario para prod de los workspaces
|
||||||
|
RUN pnpm install --frozen-lockfile --prefer-offline
|
||||||
|
|
||||||
|
# IMPORTANTE: build de paquetes buildables (utils, logger, etc.) primero
|
||||||
|
# Turborepo asegura orden con "dependsOn". Un único comando:
|
||||||
|
RUN pnpm -w turbo run build --filter=@erp/factuges-server...
|
||||||
|
|
||||||
|
########################
|
||||||
|
# 4) Runner minimal
|
||||||
|
########################
|
||||||
|
#FROM ${NODE_IMAGE} AS runner
|
||||||
|
FROM base AS runner
|
||||||
|
ENV NODE_ENV=production TZ=UTC
|
||||||
|
|
||||||
|
# Don't run production as root
|
||||||
|
RUN addgroup --system --gid 1001 expressjs
|
||||||
|
RUN adduser --system --uid 1001 expressjs
|
||||||
|
USER expressjs
|
||||||
|
|
||||||
|
#COPY --from=builder /repo .
|
||||||
|
COPY --from=builder /repo/node_modules ./node_modules
|
||||||
|
COPY --from=builder /repo/packages ./packages
|
||||||
|
COPY --from=builder /repo/package.json ./package.json
|
||||||
|
COPY --from=builder /repo/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||||
|
COPY --from=builder /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||||
|
|
||||||
|
COPY --from=builder /repo/apps/server/dist ./apps/server/dist
|
||||||
|
COPY --from=builder /repo/apps/server/.env.production ./apps/server/dist/.env
|
||||||
|
COPY --from=builder /repo/apps/server/node_modules ./apps/server/node_modules
|
||||||
|
COPY --from=builder /repo/apps/server/package.json ./apps/server/package.json
|
||||||
|
|
||||||
|
|
||||||
|
# Salud del contenedor (ajusta puerto/endpoint)
|
||||||
|
#HEALTHCHECK --interval=20s --timeout=3s --retries=5 \
|
||||||
|
# CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3002)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||||
|
|
||||||
|
EXPOSE 3002
|
||||||
|
CMD ["pnpm","exec", "node", "--env-file=apps/server/dist/.env", "apps/server/dist/index.js"]
|
||||||
@ -1,7 +1,7 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=production
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=3002
|
PORT=3002
|
||||||
FRONTEND_URL=http://localhost:5173
|
FRONTEND_URL=http://factuges.rodax-software.local
|
||||||
|
|
||||||
|
|
||||||
DB_DIALECT=mysql
|
DB_DIALECT=mysql
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
# server/Dockerfile
|
|
||||||
FROM node:22.13.1
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install --production
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
@ -1,10 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/server",
|
"name": "@erp/factuges-server",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup src/index.ts --config tsup.config.ts",
|
"build": "tsup src/index.ts --config tsup.config.ts",
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import { DateTime } from "luxon";
|
|||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { createApp } from "./app";
|
import { createApp } from "./app.ts";
|
||||||
import { ENV } from "./config";
|
import { tryConnectToDatabase } from "./config/database.ts";
|
||||||
import { tryConnectToDatabase } from "./config/database";
|
import { ENV } from "./config/index.ts";
|
||||||
import { registerHealthRoutes } from "./health";
|
import { registerHealthRoutes } from "./health.ts";
|
||||||
import { listRoutes, logger } from "./lib";
|
import { listRoutes, logger } from "./lib/index.ts";
|
||||||
import { initModules } from "./lib/modules";
|
import { initModules } from "./lib/modules/index.ts";
|
||||||
import { registerModules } from "./register-modules";
|
import { registerModules } from "./register-modules.ts";
|
||||||
|
|
||||||
const API_BASE_PATH = "/api/v1";
|
const API_BASE_PATH = "/api/v1";
|
||||||
|
|
||||||
@ -208,9 +208,23 @@ process.on("uncaughtException", async (error: Error) => {
|
|||||||
const now = DateTime.now();
|
const now = DateTime.now();
|
||||||
logger.info(`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`);
|
logger.info(`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`);
|
||||||
logger.info(`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`);
|
logger.info(`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`);
|
||||||
logger.info(`Environment: ${currentState.environment}`);
|
|
||||||
logger.info(`Process PID: ${process.pid}`);
|
logger.info(`Process PID: ${process.pid}`);
|
||||||
|
|
||||||
|
// Mostrar variables de entorno
|
||||||
|
logger.info(`Environment: ${currentState.environment}`);
|
||||||
|
logger.info(`HOST: ${ENV.HOST}`);
|
||||||
|
logger.info(`PORT: ${ENV.PORT}`);
|
||||||
|
logger.info(`API_BASE_PATH: ${API_BASE_PATH}`);
|
||||||
|
|
||||||
|
logger.info(`FRONTEND_URL: ${ENV.FRONTEND_URL}`);
|
||||||
|
|
||||||
|
logger.info(`DB_HOST: ${ENV.DB_HOST}`);
|
||||||
|
logger.info(`DB_PORT: ${ENV.DB_PORT}`);
|
||||||
|
logger.info(`DB_NAME: ${ENV.DB_NAME}`);
|
||||||
|
logger.info(`DB_USER: ${ENV.DB_USER}`);
|
||||||
|
logger.info(`DB_LOGGING: ${ENV.DB_LOGGING}`);
|
||||||
|
logger.info(`DB_SYNC_MODE: ${ENV.DB_SYNC_MODE}`);
|
||||||
|
|
||||||
const database = await tryConnectToDatabase();
|
const database = await tryConnectToDatabase();
|
||||||
|
|
||||||
// Lógica de inicialización de DB, si procede:
|
// Lógica de inicialización de DB, si procede:
|
||||||
@ -219,7 +233,7 @@ process.on("uncaughtException", async (error: Error) => {
|
|||||||
|
|
||||||
await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger });
|
await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger });
|
||||||
|
|
||||||
// ✅ El servidor ya está listo para recibir tráfico
|
// El servidor ya está listo para recibir tráfico
|
||||||
isReady = true;
|
isReady = true;
|
||||||
logger.info("✅ Server is READY (readiness=true)");
|
logger.info("✅ Server is READY (readiness=true)");
|
||||||
logger.info(`startup_duration_ms=${DateTime.now().diff(currentState.launchedAt).toMillis()}`);
|
logger.info(`startup_duration_ms=${DateTime.now().diff(currentState.launchedAt).toMillis()}`);
|
||||||
|
|||||||
@ -2,13 +2,19 @@
|
|||||||
"extends": "@repo/typescript-config/express.json",
|
"extends": "@repo/typescript-config/express.json",
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"lib": ["ES2022"],
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["*"]
|
"@/*": ["*"]
|
||||||
},
|
},
|
||||||
"outDir": "dist"
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
|
||||||
|
"removeComments": true
|
||||||
},
|
},
|
||||||
//"files": ["src/index.ts"], // Esta opción compila sólo los archivos listados (y sus dependencias importadas).
|
//"files": ["src/index.ts"], // Esta opción compila sólo los archivos listados (y sus dependencias importadas).
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["src/**/__tests__/*", "src/**/*.mock.*", "src/**/*.test.*", "node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import { defineConfig } from "tsup";
|
import { defineConfig } from "tsup";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,23 +12,62 @@ import { defineConfig } from "tsup";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ["src/index.ts"], // punto de entrada principal
|
entry: ["src/index.ts"], // punto de entrada principal
|
||||||
format: ["esm"], // ESM nativo (Node 18+)
|
format: ["esm"], // ESM nativo (Node 18+)
|
||||||
target: "node18", // objetivo de compilación
|
target: "node22", // objetivo de compilación
|
||||||
sourcemap: true,
|
bundle: true, // genera un único bundle, evita imports rotos
|
||||||
|
sourcemap: false,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
minify: false,
|
||||||
treeshake: true,
|
treeshake: true,
|
||||||
dts: false, // opcional, genera .d.ts
|
dts: false, // opcional, genera .d.ts
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
|
platform: "node",
|
||||||
|
|
||||||
// Paquetes de npm a mantener como externos (no se incluyen)
|
// Paquetes de npm a mantener como externos (no se incluyen en el "bundle")
|
||||||
external: ["express", "zod", "react", "react-dom", "date-fns"],
|
external: [],
|
||||||
|
|
||||||
// Paquetes internos buildless que se deben incluir en el bundle
|
noExternal: ["@repo", "@erp"],
|
||||||
noExternal: [
|
|
||||||
"@erp/auth",
|
esbuildOptions(options) {
|
||||||
"@erp/core",
|
// Permite resolver imports sin extensión (.ts, .js, .mjs, etc.)
|
||||||
"@erp/customers",
|
options.resolveExtensions = [".ts", ".js", ".mjs", ".json"];
|
||||||
"@erp/customer-invoices",
|
|
||||||
"@erp/verifactu",
|
// Corrige la extensión de salida
|
||||||
"@repo/rdx-logger",
|
options.outExtension = { ".js": ".js" };
|
||||||
|
|
||||||
|
// Permite usar require en contexto ESM (Node >=18)
|
||||||
|
options.banner = {
|
||||||
|
js: `
|
||||||
|
import { createRequire } from "module";
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Plugin: fuerza a que los imports relativos se traten como locales, no "external"
|
||||||
|
esbuildPlugins: [
|
||||||
|
{
|
||||||
|
name: "fix-local-imports",
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^\.{1,2}\// }, (args) => {
|
||||||
|
const abs = path.resolve(args.resolveDir, args.path);
|
||||||
|
|
||||||
|
// Si es un directorio, intenta resolver a index.(ts|js)
|
||||||
|
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
||||||
|
const indexTs = path.join(abs, "index.ts");
|
||||||
|
const indexJs = path.join(abs, "index.js");
|
||||||
|
if (fs.existsSync(indexTs)) return { path: indexTs, external: false };
|
||||||
|
if (fs.existsSync(indexJs)) return { path: indexJs, external: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si es un fichero con extensión válida
|
||||||
|
const withExts = [".ts", ".js", ".mjs"];
|
||||||
|
for (const ext of withExts) {
|
||||||
|
if (fs.existsSync(abs + ext)) return { path: abs + ext, external: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { path: abs, external: false };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/web",
|
"name": "@erp/factuges-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host --clearScreen false",
|
"dev": "vite --host --clearScreen false",
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
name: factuges
|
|
||||||
|
|
||||||
services:
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:latest
|
|
||||||
container_name: mariadb
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
MYSQL_ROOT_PASSWORD: rootpass
|
|
||||||
MYSQL_DATABASE: factuges_db
|
|
||||||
MYSQL_USER: factuges_usr
|
|
||||||
MYSQL_PASSWORD: factuges_pass
|
|
||||||
volumes:
|
|
||||||
- mariadb_data:/var/lib/mysql
|
|
||||||
ports:
|
|
||||||
- "3306:3306"
|
|
||||||
|
|
||||||
phpmyadmin:
|
|
||||||
image: phpmyadmin/phpmyadmin
|
|
||||||
container_name: phpmyadmin
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
PMA_HOST: mariadb
|
|
||||||
MYSQL_ROOT_PASSWORD: rootpass
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
depends_on:
|
|
||||||
- mariadb
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
mariadb_data:
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/auth",
|
"name": "@erp/auth",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/core",
|
"name": "@erp/core",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/customer-invoices",
|
"name": "@erp/customer-invoices",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -10,7 +10,10 @@ export enum INVOICE_STATUS {
|
|||||||
SENT = "sent", // <- Proforma
|
SENT = "sent", // <- Proforma
|
||||||
APPROVED = "approved", // <- Proforma
|
APPROVED = "approved", // <- Proforma
|
||||||
REJECTED = "rejected", // <- Proforma
|
REJECTED = "rejected", // <- Proforma
|
||||||
ISSUED = "issued", // <- Factura y enviada a Veri*Factu
|
|
||||||
|
// issued <- (si is_proforma === true) => Es una proforma (histórica)
|
||||||
|
// issued <- (si is_proforma === false) => Factura y enviada a Veri*Factu
|
||||||
|
ISSUED = "issued",
|
||||||
}
|
}
|
||||||
export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> {
|
export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> {
|
||||||
private static readonly ALLOWED_STATUSES = ["draft", "sent", "approved", "rejected", "issued"];
|
private static readonly ALLOWED_STATUSES = ["draft", "sent", "approved", "rejected", "issued"];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/customers",
|
"name": "@erp/customers",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/doc-numbering",
|
"name": "@erp/doc-numbering",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/verifactu",
|
"name": "@erp/verifactu",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-criteria",
|
"name": "@repo/rdx-criteria",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-ddd",
|
"name": "@repo/rdx-ddd",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-logger",
|
"name": "@repo/rdx-logger",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-utils",
|
"name": "@repo/rdx-utils",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
219
scripts/build-api.sh
Executable file
219
scripts/build-api.sh
Executable file
@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_VERSION="1.0.3"
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# FACTUGES Build Script
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Build + Export de la API y/o
|
||||||
|
# compilación de la Web (por compañía)
|
||||||
|
# =====================================================
|
||||||
|
# Uso:
|
||||||
|
# ./scripts/build-api.sh <company> [--api|web|all] [--load]
|
||||||
|
#
|
||||||
|
# Ejemplos:
|
||||||
|
# ./scripts/build-api.sh acme --api --load # solo API + carga en Docker local
|
||||||
|
# ./scripts/build-api.sh acme --web # solo web
|
||||||
|
# ./scripts/build-api.sh acme # API + web (por defecto)
|
||||||
|
#
|
||||||
|
# Funcionalidades:
|
||||||
|
# - Detecta automáticamente el nombre, versión y puerto de la API
|
||||||
|
# - Compila la web React (Vite)
|
||||||
|
# - Copia los estáticos a out/<company>/web/dist
|
||||||
|
# - Genera imagen Docker etiquetada por compañía + versión + latest
|
||||||
|
# - Guarda la imagen .tar en out/<company>/api/
|
||||||
|
# - Carga la imagen en Docker (opcional)
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
# --- Configuración base ---
|
||||||
|
COMPANY="${1:-}"
|
||||||
|
MODE="all" # api | web | all
|
||||||
|
LOAD=false
|
||||||
|
|
||||||
|
# --- Parseo de flags ---
|
||||||
|
for arg in "${@:2}"; do
|
||||||
|
case "$arg" in
|
||||||
|
--api) MODE="api" ;;
|
||||||
|
--web) MODE="web" ;;
|
||||||
|
--all) MODE="all" ;;
|
||||||
|
--load) LOAD=true ;;
|
||||||
|
*) echo "⚠️ Argumento desconocido: $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Paths base ---
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(realpath "${SCRIPT_DIR}/..")"
|
||||||
|
SERVER_DIR="${PROJECT_DIR}/apps/server"
|
||||||
|
WEB_DIR="${PROJECT_DIR}/apps/web"
|
||||||
|
OUT_API_DIR="${PROJECT_DIR}/out/${COMPANY}/api"
|
||||||
|
OUT_WEB_DIR="${PROJECT_DIR}/out/${COMPANY}/web"
|
||||||
|
|
||||||
|
if [[ -z "$COMPANY" ]]; then
|
||||||
|
echo "❌ Error: debes indicar la compañía. Ejemplo: ./scripts/build-api.sh acme [--api|--web|--all] [--load]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Verificaciones mínimas ---
|
||||||
|
[[ -d "$SERVER_DIR" ]] || { echo "❌ Falta ${SERVER_DIR}"; exit 1; }
|
||||||
|
[[ -d "$WEB_DIR" ]] || { echo "❌ Falta ${WEB_DIR}"; exit 1; }
|
||||||
|
|
||||||
|
|
||||||
|
# --- Detectar nombre y versión de la API ---
|
||||||
|
IMAGE_NAME=$(node -p "require('${SERVER_DIR}/package.json').name.replace(/^@.*\\//, '')" 2>/dev/null || echo "api")
|
||||||
|
API_VERSION=$(node -p "require('${SERVER_DIR}/package.json').version" 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# --- Detectar versión de la web ---
|
||||||
|
WEB_VERSION=$(node -p "require('${WEB_DIR}/package.json').version" 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# --- Detectar puerto ---
|
||||||
|
PORT=""
|
||||||
|
if [[ -f "${SERVER_DIR}/.env" ]]; then
|
||||||
|
PORT=$(grep -E '^PORT=' "${SERVER_DIR}/.env" | cut -d'=' -f2 | tr -d '"')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$PORT" && -f "${PROJECT_DIR}/Dockerfile" ]]; then
|
||||||
|
PORT=$(grep -E '^EXPOSE ' "${PROJECT_DIR}/Dockerfile" | awk '{print $2}' | head -n1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORT="${PORT:-3002}" # valor por defecto
|
||||||
|
|
||||||
|
# --- 3. Etiquetas e información ---
|
||||||
|
DATE=$(date +'%Y%m%d-%H%M%S')
|
||||||
|
ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
USER_NAME=$(whoami)
|
||||||
|
GIT_HASH=$(git -C "$PROJECT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
|
GIT_BRANCH=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
IMAGE_TAG_V="${IMAGE_NAME}:${COMPANY}-v${API_VERSION}"
|
||||||
|
IMAGE_TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest"
|
||||||
|
|
||||||
|
mkdir -p "$OUT_API_DIR" "$OUT_WEB_DIR"
|
||||||
|
rm -rf "${OUT_API_DIR:?}/"*
|
||||||
|
rm -rf "${OUT_WEB_DIR:?}/"*
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "-------------------------------------------------------"
|
||||||
|
echo " FACTUGES Build Script v${SCRIPT_VERSION}"
|
||||||
|
echo " Construyendo entorno para compañía '${COMPANY}'"
|
||||||
|
echo " Proyecto: ${IMAGE_NAME}"
|
||||||
|
echo " Versión API: ${API_VERSION}"
|
||||||
|
echo " Versión Web: ${WEB_VERSION}"
|
||||||
|
echo " Puerto API: ${PORT}"
|
||||||
|
echo " Etiquetas: ${IMAGE_TAG_V}, ${IMAGE_TAG_LATEST}"
|
||||||
|
echo " API out: ${OUT_API_DIR}"
|
||||||
|
echo " WEB out: ${OUT_WEB_DIR}"
|
||||||
|
echo " Modo: ${MODE}"
|
||||||
|
echo " Cargar: ${LOAD}"
|
||||||
|
echo "-------------------------------------------------------"
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 1️⃣ WEB
|
||||||
|
# =====================================================
|
||||||
|
if [[ "$MODE" == "web" || "$MODE" == "all" ]]; then
|
||||||
|
echo "🌐 Compilando web con Vite..."
|
||||||
|
cd "${WEB_DIR}"
|
||||||
|
|
||||||
|
# Puedes pasar variables específicas por compañía
|
||||||
|
# Ejemplo: VITE_COMPANY=acme VITE_API_BASE=https://acme.localhost/api
|
||||||
|
VITE_COMPANY="${COMPANY}" pnpm build
|
||||||
|
|
||||||
|
# Carpeta versionada
|
||||||
|
VERSION_DIR="${OUT_WEB_DIR}/versions/v${WEB_VERSION}-${DATE}"
|
||||||
|
mkdir -p "${VERSION_DIR}/dist"
|
||||||
|
rm -rf "${VERSION_DIR}/dist"/*
|
||||||
|
cp -r "${WEB_DIR}/dist/"* "${VERSION_DIR}/dist/"
|
||||||
|
|
||||||
|
# Enlace simbólico 'latest' → versión recién compilada
|
||||||
|
ln -sfn "${VERSION_DIR}" "${OUT_WEB_DIR}/latest"
|
||||||
|
|
||||||
|
# Manifest JSON de la web
|
||||||
|
cat > "${VERSION_DIR}/manifest.json" <<EOF
|
||||||
|
{
|
||||||
|
"type": "web",
|
||||||
|
"company": "${COMPANY}",
|
||||||
|
"version": "${WEB_VERSION}",
|
||||||
|
"buildTime": "${ISO_DATE}",
|
||||||
|
"user": "${USER_NAME}",
|
||||||
|
"git": {
|
||||||
|
"branch": "${GIT_BRANCH}",
|
||||||
|
"commit": "${GIT_HASH}"
|
||||||
|
},
|
||||||
|
"source": "${PROJECT_DIR}/apps/web",
|
||||||
|
"dist": "${VERSION_DIR}/dist"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Web v${WEB_VERSION} compilada y copiada a ${VERSION_DIR}"
|
||||||
|
echo " 🔗 Enlace 'latest' actualizado a ${OUT_WEB_DIR}/latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 2️⃣ API
|
||||||
|
# =====================================================
|
||||||
|
if [[ "$MODE" == "api" || "$MODE" == "all" ]]; then
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
echo "🐳 Construyendo imagen Docker..."
|
||||||
|
|
||||||
|
docker build --no-cache --debug -t "${IMAGE_TAG_V}" -t "${IMAGE_TAG_LATEST}" \
|
||||||
|
--build-arg PORT="${PORT}" \
|
||||||
|
-f "${PROJECT_DIR}/Dockerfile" "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
echo "✅ Imagen Docker construida correctamente"
|
||||||
|
|
||||||
|
TAR_FILE_V="${OUT_API_DIR}/${IMAGE_NAME}-${COMPANY}-v${API_VERSION}-${DATE}.tar"
|
||||||
|
TAR_FILE_LATEST="${OUT_API_DIR}/${IMAGE_NAME}-${COMPANY}-latest.tar"
|
||||||
|
|
||||||
|
docker save "${IMAGE_TAG_V}" "${IMAGE_TAG_LATEST}" -o "${TAR_FILE_V}"
|
||||||
|
cp -f "${TAR_FILE_V}" "${TAR_FILE_LATEST}"
|
||||||
|
|
||||||
|
echo "📦 Imagen guardada:"
|
||||||
|
echo " ${TAR_FILE_V}"
|
||||||
|
echo " ${TAR_FILE_LATEST}"
|
||||||
|
|
||||||
|
# Manifest JSON de la API
|
||||||
|
cat > "${OUT_API_DIR}/${IMAGE_NAME}-${COMPANY}-v${API_VERSION}-${DATE}-manifest.json" <<EOF
|
||||||
|
{
|
||||||
|
"type": "api",
|
||||||
|
"company": "${COMPANY}",
|
||||||
|
"image": "${IMAGE_TAG_V}",
|
||||||
|
"version": "${API_VERSION}",
|
||||||
|
"buildTime": "${ISO_DATE}",
|
||||||
|
"user": "${USER_NAME}",
|
||||||
|
"git": {
|
||||||
|
"branch": "${GIT_BRANCH}",
|
||||||
|
"commit": "${GIT_HASH}"
|
||||||
|
},
|
||||||
|
"port": "${PORT}",
|
||||||
|
"files": {
|
||||||
|
"versioned": "${TAR_FILE_V}",
|
||||||
|
"latest": "${TAR_FILE_LATEST}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "📦 API manifest generado en ${OUT_API_DIR}/manifest-v${API_VERSION}-${DATE}.json"
|
||||||
|
|
||||||
|
if [[ "$LOAD" == true ]]; then
|
||||||
|
echo "📥 Cargando imagen en Docker local..."
|
||||||
|
docker load -i "${TAR_FILE_V}"
|
||||||
|
echo "✅ Imagen cargada en Docker local"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# 3️⃣ Resumen
|
||||||
|
# =====================================================
|
||||||
|
echo "-------------------------------------------------------"
|
||||||
|
echo "🎯 Resultado final para '${COMPANY}'"
|
||||||
|
[[ "$MODE" == "web" || "$MODE" == "all" ]] && echo " 🌐 Web: v${WEB_VERSION} → ${OUT_WEB_DIR}/versions/v${WEB_VERSION}-${DATE}"
|
||||||
|
[[ "$MODE" == "api" || "$MODE" == "all" ]] && echo " 🐳 API: v${API_VERSION} → ${OUT_API_DIR}"
|
||||||
|
echo "🧩 Script version: ${SCRIPT_VERSION} - FIN"
|
||||||
|
echo "-------------------------------------------------------"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@repo/typescript-config/root.json",
|
|
||||||
"include": ["apps", "modules", "packages"]
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user