Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 2, 2025

Converts Express/EJS app to static HTML for GitHub Pages hosting. EJS templates are rendered at build time with default context values, and output is deployed to gh-pages branch on push to main.

Changes

  • scripts/build-static.js - Build script that:

    • Renders EJS templates from views/docs/*.html
    • Copies public/docs/public/
    • Ignores partials/ and includes/ directories
    • Creates docs/index.html from shop/home.ejs
    • Writes .nojekyll to prevent Jekyll processing
  • .github/workflows/deploy-pages.yml - CI workflow using peaceiris/actions-gh-pages@v3 to deploy docs/ to gh-pages branch

  • package.json - Added build:static script and fs-extra/glob dev dependencies

  • .gitignore - Excludes docs/ build output

Usage

npm ci
npm run build:static
# open docs/index.html to verify

Notes

Templates are rendered with empty/default context values. Pages requiring dynamic data (DB queries, session state) will fail to render and are logged as warnings—these need manual conversion or a data source.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com//advisories
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Goal: Convert the existing Express/EJS site into a static build and automatically deploy it to GitHub Pages (gh-pages branch) on every push to main. The repository currently uses EJS templates in views/ and static assets in public/. GitHub Pages cannot run Node/Express, so we will render EJS into plain HTML and publish the output.

Required changes to implement:

  1. Add a build script at scripts/build-static.js that:
  • Renders EJS templates from views/ into HTML files under docs/ (replace .ejs with .html)
  • Copies the public/ folder to docs/public to preserve static assets
  • Ignores partials/ directories and files starting with '_' (partials)
  • Ensures docs/index.html exists by using views/index.ejs if present
  • Writes a .nojekyll file into docs/ to prevent GitHub Pages from ignoring files starting with underscore
  • Exits non-zero on unexpected errors and logs progress

File content (scripts/build-static.js):

const path = require('path');
const fs = require('fs-extra');
const glob = require('glob');
const ejs = require('ejs');

const repoRoot = path.join(__dirname, '..');
const viewsDir = path.join(repoRoot, 'views');
const publicDir = path.join(repoRoot, 'public');
const outDir = path.join(repoRoot, 'docs');

try {
  // Cleanup and recreate output dir
  fs.removeSync(outDir);
  fs.ensureDirSync(outDir);

  // Copy static assets
  if (fs.existsSync(publicDir)) {
    fs.copySync(publicDir, path.join(outDir, 'public'));
    console.log('Copied public/ -> docs/public');
  }

  // Find all .ejs files (skip partials or files starting with _)
  const ejsFiles = glob.sync('**/*.ejs', {
    cwd: viewsDir,
    nodir: true,
    ignore: ['**/partials/**', '**/_*.ejs']
  });

  if (ejsFiles.length === 0) {
    console.warn('No EJS templates found in views/. Update the script if your templates are elsewhere.');
  }

  (async () => {
    for (const rel of ejsFiles) {
      const srcPath = path.join(viewsDir, rel);
      let outRel = rel.replace(/\.ejs$/, '.html');
      const outPath = path.join(outDir, outRel);
      fs.ensureDirSync(path.dirname(outPath));

      try {
        // Render with empty context. If templates need data, update this part.
        const html = await ejs.renderFile(srcPath, {}, { root: viewsDir, async: true });
        fs.writeFileSync(outPath, html, 'utf8');
        console.log(`Rendered ${rel} -> ${path.relative(repoRoot, outPath)}`);
      } catch (err) {
        console.error(`Failed rendering ${rel}:`, err);
      }
    }

    // Ensure top-level index.html exists
    const candidate = path.join(outDir, 'index.html');
    if (!fs.existsSync(candidate)) {
      const fallbackIndex = ejsFiles.find(f => /index\.ejs$/.test(f));
      if (fallbackIndex) {
        const src = path.join(viewsDir, fallbackIndex);
        const html = await ejs.renderFile(src, {}, { root: viewsDir, async: true });
        fs.writeFileSync(candidate, html, 'utf8');
        console.log(`Created docs/index.html from ${fallbackIndex}`);
      } else {
        console.warn('No index.html created. Add an index.ejs, or modify the script mapping.');
      }
    }

    // Create .nojekyll to allow files/folders starting with _
    fs.writeFileSync(path.join(outDir, '.nojekyll'), '', 'utf8');
    console.log('Wrote docs/.nojekyll');

    console.log('Static build complete.');
  })();
} catch (err) {
  console.error('Static build failed:', err);
  process.exit(1);
}
  1. Update package.json to add an npm script:
  • Add to "scripts":
    "build:static": "node scripts/build-static.js"

If package.json already has a scripts object, only add the new key.

  1. Add a GitHub Actions workflow at .github/workflows/deploy-pages.yml that:
  • Runs on push to main
  • Uses Node 18
  • Installs dependencies (npm ci)
  • Runs npm run build:static
  • Deploys the contents of docs/ to the gh-pages branch using peaceiris/actions-gh-pages@v3

Workflow content (.github/workflows/deploy-pages.yml):

name: Build and deploy static site to gh-pages

on:
  push:
    branches:
      - main

permissions:
  contents: write
  pages: write
  id-token: write

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build static site
        run: npm run build:static

      - name: Deploy to gh-pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs
  1. Optional: If you prefer GitHub Pages to read from main/docs (instead of gh-pages), the workflow can commit docs/ back to main. Current plan: publish to gh-pages branch and instruct Pages to use gh-pages branch in repo settings. The action will create/update gh-pages automatically.

  2. Tests to verify locally after merge (instructions for you):

  • npm ci
  • npm run build:static
  • open docs/i...

This pull request was created as a result of the following prompt from Copilot chat.

Goal: Convert the existing Express/EJS site into a static build and automatically deploy it to GitHub Pages (gh-pages branch) on every push to main. The repository currently uses EJS templates in views/ and static assets in public/. GitHub Pages cannot run Node/Express, so we will render EJS into plain HTML and publish the output.

Required changes to implement:

  1. Add a build script at scripts/build-static.js that:
  • Renders EJS templates from views/ into HTML files under docs/ (replace .ejs with .html)
  • Copies the public/ folder to docs/public to preserve static assets
  • Ignores partials/ directories and files starting with '_' (partials)
  • Ensures docs/index.html exists by using views/index.ejs if present
  • Writes a .nojekyll file into docs/ to prevent GitHub Pages from ignoring files starting with underscore
  • Exits non-zero on unexpected errors and logs progress

File content (scripts/build-static.js):

const path = require('path');
const fs = require('fs-extra');
const glob = require('glob');
const ejs = require('ejs');

const repoRoot = path.join(__dirname, '..');
const viewsDir = path.join(repoRoot, 'views');
const publicDir = path.join(repoRoot, 'public');
const outDir = path.join(repoRoot, 'docs');

try {
  // Cleanup and recreate output dir
  fs.removeSync(outDir);
  fs.ensureDirSync(outDir);

  // Copy static assets
  if (fs.existsSync(publicDir)) {
    fs.copySync(publicDir, path.join(outDir, 'public'));
    console.log('Copied public/ -> docs/public');
  }

  // Find all .ejs files (skip partials or files starting with _)
  const ejsFiles = glob.sync('**/*.ejs', {
    cwd: viewsDir,
    nodir: true,
    ignore: ['**/partials/**', '**/_*.ejs']
  });

  if (ejsFiles.length === 0) {
    console.warn('No EJS templates found in views/. Update the script if your templates are elsewhere.');
  }

  (async () => {
    for (const rel of ejsFiles) {
      const srcPath = path.join(viewsDir, rel);
      let outRel = rel.replace(/\.ejs$/, '.html');
      const outPath = path.join(outDir, outRel);
      fs.ensureDirSync(path.dirname(outPath));

      try {
        // Render with empty context. If templates need data, update this part.
        const html = await ejs.renderFile(srcPath, {}, { root: viewsDir, async: true });
        fs.writeFileSync(outPath, html, 'utf8');
        console.log(`Rendered ${rel} -> ${path.relative(repoRoot, outPath)}`);
      } catch (err) {
        console.error(`Failed rendering ${rel}:`, err);
      }
    }

    // Ensure top-level index.html exists
    const candidate = path.join(outDir, 'index.html');
    if (!fs.existsSync(candidate)) {
      const fallbackIndex = ejsFiles.find(f => /index\.ejs$/.test(f));
      if (fallbackIndex) {
        const src = path.join(viewsDir, fallbackIndex);
        const html = await ejs.renderFile(src, {}, { root: viewsDir, async: true });
        fs.writeFileSync(candidate, html, 'utf8');
        console.log(`Created docs/index.html from ${fallbackIndex}`);
      } else {
        console.warn('No index.html created. Add an index.ejs, or modify the script mapping.');
      }
    }

    // Create .nojekyll to allow files/folders starting with _
    fs.writeFileSync(path.join(outDir, '.nojekyll'), '', 'utf8');
    console.log('Wrote docs/.nojekyll');

    console.log('Static build complete.');
  })();
} catch (err) {
  console.error('Static build failed:', err);
  process.exit(1);
}
  1. Update package.json to add an npm script:
  • Add to "scripts":
    "build:static": "node scripts/build-static.js"

If package.json already has a scripts object, only add the new key.

  1. Add a GitHub Actions workflow at .github/workflows/deploy-pages.yml that:
  • Runs on push to main
  • Uses Node 18
  • Installs dependencies (npm ci)
  • Runs npm run build:static
  • Deploys the contents of docs/ to the gh-pages branch using peaceiris/actions-gh-pages@v3

Workflow content (.github/workflows/deploy-pages.yml):

name: Build and deploy static site to gh-pages

on:
  push:
    branches:
      - main

permissions:
  contents: write
  pages: write
  id-token: write

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build static site
        run: npm run build:static

      - name: Deploy to gh-pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs
  1. Optional: If you prefer GitHub Pages to read from main/docs (instead of gh-pages), the workflow can commit docs/ back to main. Current plan: publish to gh-pages branch and instruct Pages to use gh-pages branch in repo settings. The action will create/update gh-pages automatically.

  2. Tests to verify locally after merge (instructions for you):

  • npm ci
  • npm run build:static
  • open docs/index.html locally to verify

Deliverables in the PR:

  • scripts/build-static.js (new)
  • .github/workflows/deploy-pages.yml (new)
  • package.json modified to include "build:static" script

Constraints and notes:

  • The script renders templates with an empty context. If your templates depend on dynamic data (DB calls, runtime variables), they will need a data source or manual conversion.
  • If your app uses server-only routes to produce pages, full parity will require migrating that logic into static data files or a different host.
  • The build intentionally ignores partials (files/folders that start with underscore or are in a partials/ folder). Adjust if your project uses a different convention.

Please create a branch off main and open a pull request with these changes so that the CI builds and publishes docs/ to the gh-pages branch automatically.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits December 2, 2025 21:35
…ges deployment

Co-authored-by: hammad-521 <110757128+hammad-521@users.noreply.github.com>
…build script

Co-authored-by: hammad-521 <110757128+hammad-521@users.noreply.github.com>
Copilot AI changed the title [WIP] Convert Express/EJS site to static build for GitHub Pages Add static build pipeline for GitHub Pages deployment Dec 2, 2025
Copilot AI requested a review from hammad-521 December 2, 2025 21:39
Repository owner deleted a comment from Copilot AI Dec 2, 2025
Copilot AI requested a review from hammad-521 December 2, 2025 21:45
@hammad-521 hammad-521 marked this pull request as ready for review December 2, 2025 21:45
@hammad-521 hammad-521 merged commit 27640f1 into main Dec 2, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants