diff --git a/.github/workflows/docker-publish-dockerhub.yml b/.github/workflows/docker-publish-dockerhub.yml index 2990b9b..01f95ac 100644 --- a/.github/workflows/docker-publish-dockerhub.yml +++ b/.github/workflows/docker-publish-dockerhub.yml @@ -29,6 +29,23 @@ jobs: with: fetch-depth: 0 # Needed to get all tags for versioning + # Set up Node.js for frontend build + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + # Install dependencies + - name: Install dependencies + run: npm ci + + # Build frontend assets + - name: Build frontend assets + run: | + node build.js + ls -la static/dist/ + # Set version information - name: Set Version id: version diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7fc262c..479fd70 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -30,6 +30,23 @@ jobs: with: fetch-depth: 0 # Needed to get all tags for versioning + # Set up Node.js for frontend build + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + # Install dependencies + - name: Install dependencies + run: npm ci + + # Build frontend assets + - name: Build frontend assets + run: | + node build.js + ls -la static/dist/ + # Set version information - name: Set Version id: version diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 3a7fcb0..8b9cb28 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -27,6 +27,26 @@ jobs: with: fetch-depth: 0 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install Node.js dependencies + run: npm ci + + - name: Build frontend assets + run: | + # Build JavaScript and CSS assets + node build.js + + # Ensure the dist directory exists + mkdir -p static/dist + + # Verify the build output + ls -la static/dist + - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/.gitignore b/.gitignore index b8726fd..935422f 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,8 @@ backups/ /destination/ /archive/ +# Ignore the dist directory +static/dist/ # Ignore binaries gomft \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 57f166a..0785d97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,32 @@ +# Build frontend assets +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +# Copy all files needed for the build first +COPY package.json package-lock.json ./ +COPY build.js ./ +COPY static/ ./static/ +COPY tailwind.config.js ./ + +# Debug: Show the contents of build.js +RUN echo "Contents of build.js:" && cat build.js + +# Remove the build script from postinstall +RUN sed -i 's/"postinstall": "npm run build",//' package.json + +# Install dependencies and build with verbose output +RUN npm ci && \ + echo "Building frontend assets..." && \ + node build.js && \ + echo "Build complete. Contents of dist:" && \ + ls -la static/dist/ && \ + echo "Sample of app.js:" && \ + head -n 10 static/dist/app.js && \ + echo "Sample of app.css:" && \ + head -n 10 static/dist/app.css + +# Go build stage FROM golang:1.24-alpine AS builder WORKDIR /app @@ -17,7 +46,20 @@ RUN go install github.com/a-h/templ/cmd/templ@latest COPY go.mod go.sum ./ RUN go mod download -# Copy the rest of the source code +# Create static directory structure +RUN mkdir -p /app/static/dist + +# Copy built frontend assets from frontend-builder BEFORE copying Go source +COPY --from=frontend-builder /app/static/dist/ /app/static/dist/ + +# Copy the rest of the static files +COPY static/ /app/static/ + +# Verify static files are in place before Go build +RUN echo "Verifying static files before Go build:" && \ + ls -la /app/static/dist/ + +# Now copy the rest of the source code COPY . . # Generate template files from .templ files @@ -61,8 +103,7 @@ RUN addgroup -g ${GID} ${USERNAME} && \ COPY --from=builder /app/gomft /app/ COPY --from=builder /usr/local/bin/rclone /usr/local/bin/rclone -# Copy static files and configurations -COPY static/ /app/static/ +# Copy components COPY components/ /app/components/ # Copy entrypoint script diff --git a/build.js b/build.js new file mode 100644 index 0000000..4d39828 --- /dev/null +++ b/build.js @@ -0,0 +1,122 @@ +import * as esbuild from 'esbuild'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const isWatch = process.argv.includes('--watch'); + +// Ensure the dist directory exists +const distDir = path.join(__dirname, 'static', 'dist'); +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +// Copy Font Awesome files +const fontAwesomeSrcDir = path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free'); +const fontAwesomeDestDir = path.join(distDir, 'fontawesome'); + +// Copy CSS files +const cssFiles = [ + 'css/all.min.css', + 'css/fontawesome.min.css', + 'css/solid.min.css', + 'css/regular.min.css', + 'css/brands.min.css' +]; + +cssFiles.forEach(file => { + const srcFile = path.join(fontAwesomeSrcDir, file); + const destFile = path.join(fontAwesomeDestDir, file); + const destDir = path.dirname(destFile); + + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + if (fs.existsSync(srcFile)) { + fs.copyFileSync(srcFile, destFile); + } +}); + +// Copy webfonts +const webfontsSrcDir = path.join(fontAwesomeSrcDir, 'webfonts'); +const webfontsDestDir = path.join(fontAwesomeDestDir, 'webfonts'); + +if (!fs.existsSync(webfontsDestDir)) { + fs.mkdirSync(webfontsDestDir, { recursive: true }); +} + +fs.readdirSync(webfontsSrcDir).forEach(file => { + fs.copyFileSync( + path.join(webfontsSrcDir, file), + path.join(webfontsDestDir, file) + ); +}); + +const commonConfig = { + sourcemap: true, + minify: true, + bundle: true, + platform: 'browser', + target: ['es2020'], +}; + +async function buildTailwind() { + console.log('Building Tailwind CSS...'); + execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --minify'); +} + +async function build() { + try { + // Build vendor JavaScript bundle (CDN dependencies) + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/vendor.js'], + outfile: 'static/dist/vendor.js', + format: 'iife', + }); + + // Build application JavaScript + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/app.js'], + outfile: 'static/dist/app.js', + format: 'iife', + }); + + // Build initialization JavaScript + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/init.js'], + outfile: 'static/dist/init.js', + format: 'iife', + }); + + // Build CSS with Tailwind + await buildTailwind(); + + console.log('Build completed successfully!'); + } catch (error) { + console.error('Build failed:', error); + process.exit(1); + } +} + +if (isWatch) { + // Watch mode + console.log('Starting watch mode...'); + const ctx = await esbuild.context(commonConfig); + await ctx.watch(); + + // Watch Tailwind CSS + execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --watch'); + + console.log('Watching for changes...'); +} else { + // Single build + build(); +} \ No newline at end of file diff --git a/components/admin_roles.templ b/components/admin_roles.templ index a39213c..3ba3de0 100644 --- a/components/admin_roles.templ +++ b/components/admin_roles.templ @@ -87,7 +87,7 @@ templ AdminRoles(ctx context.Context, data RolesData) { @@ -269,7 +269,10 @@ templ AdminRoles(ctx context.Context, data RolesData) { // RoleDialog for confirmation actions templ RoleDialog(id string, title string, message string, confirmClass string, confirmText string, action string, roleID uint, roleName string) { -