UNPKG

@quasarbright/projection

Version:

A static site generator that creates a beautiful, interactive gallery to showcase your coding projects. Features search, filtering, tags, responsive design, and an admin UI.

464 lines (412 loc) 21.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.showDeployHelp = showDeployHelp; exports.deploy = deploy; const git_helper_1 = require("../utils/git-helper"); const deployment_config_1 = require("../utils/deployment-config"); const project_file_finder_1 = require("../utils/project-file-finder"); const build_helper_1 = require("../utils/build-helper"); const logger_1 = require("../utils/logger"); const errors_1 = require("../utils/errors"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const ghpages = __importStar(require("gh-pages")); /** * Display comprehensive help documentation for the deploy command */ function showDeployHelp() { console.log(` ${'\x1b[1m'}Projection Deploy - GitHub Pages Deployment${'\x1b[0m'} ${'\x1b[1m'}DESCRIPTION:${'\x1b[0m'} Deploy your portfolio site to GitHub Pages with a single command. Automatically builds your site and pushes it to the gh-pages branch. ${'\x1b[1m'}USAGE:${'\x1b[0m'} projection deploy [options] ${'\x1b[1m'}OPTIONS:${'\x1b[0m'} -b, --branch <branch> Target branch for deployment Default: gh-pages -m, --message <message> Custom commit message for deployment Default: "Deploy to GitHub Pages - <timestamp>" -r, --remote <remote> Git remote to push to Default: origin -d, --dir <dir> Build directory to deploy Default: dist --no-build Skip the build step and deploy existing files Use this if you've already built your site --dry-run Simulate deployment without actually pushing Useful for testing your configuration --force Force push to remote (overwrites remote history) ${'\x1b[33m'}⚠️ Use with caution!${'\x1b[0m'} --help Show this help message ${'\x1b[1m'}EXAMPLES:${'\x1b[0m'} ${'\x1b[36m'}# Basic deployment (most common)${'\x1b[0m'} projection deploy ${'\x1b[36m'}# Deploy to a custom branch${'\x1b[0m'} projection deploy --branch main ${'\x1b[36m'}# Deploy with a custom commit message${'\x1b[0m'} projection deploy --message "Update portfolio with new projects" ${'\x1b[36m'}# Deploy without rebuilding (use existing dist folder)${'\x1b[0m'} projection deploy --no-build ${'\x1b[36m'}# Test deployment without actually pushing${'\x1b[0m'} projection deploy --dry-run ${'\x1b[36m'}# Deploy to a different remote${'\x1b[0m'} projection deploy --remote upstream ${'\x1b[36m'}# Force push (overwrites remote history)${'\x1b[0m'} projection deploy --force ${'\x1b[36m'}# Deploy from a custom build directory${'\x1b[0m'} projection deploy --dir build ${'\x1b[36m'}# Combine multiple options${'\x1b[0m'} projection deploy --branch gh-pages --message "v2.0 release" --no-build ${'\x1b[1m'}CONFIGURATION:${'\x1b[0m'} You can configure deployment settings in your projection.config.json: ${'\x1b[2m'}{ title: "My Portfolio", baseUrl: "/my-repo/", homepage: "portfolio.example.com", deployBranch: "gh-pages" }${'\x1b[0m'} ${'\x1b[1m'}baseUrl:${'\x1b[0m'} The URL path where your site will be hosted For project sites: /repository-name/ For user sites: / ${'\x1b[1m'}homepage:${'\x1b[0m'} Custom domain for your site (creates CNAME file) Example: "portfolio.example.com" ${'\x1b[1m'}deployBranch:${'\x1b[0m'} Default branch for deployment (default: gh-pages) ${'\x1b[1m'}REQUIREMENTS:${'\x1b[0m'} Before deploying, ensure you have: ${'\x1b[32m'}${'\x1b[0m'} Git installed and configured ${'\x1b[32m'}${'\x1b[0m'} A Git repository initialized (git init) ${'\x1b[32m'}${'\x1b[0m'} A remote configured (git remote add origin <url>) ${'\x1b[32m'}${'\x1b[0m'} A projects file (projects.yaml, projects.yml, or projects.json) ${'\x1b[32m'}${'\x1b[0m'} Git credentials configured for pushing ${'\x1b[1m'}FIRST-TIME SETUP:${'\x1b[0m'} 1. Initialize Git repository: ${'\x1b[2m'}git init${'\x1b[0m'} 2. Add your GitHub repository as remote: ${'\x1b[2m'}git remote add origin https://github.com/username/repo.git${'\x1b[0m'} 3. Configure baseUrl in projection.config.json: ${'\x1b[2m'}baseUrl: "/repo/"${'\x1b[0m'} 4. Deploy your site: ${'\x1b[2m'}projection deploy${'\x1b[0m'} 5. Enable GitHub Pages in repository settings: - Go to Settings → Pages - Set source to "gh-pages" branch - Save and wait for deployment ${'\x1b[1m'}TROUBLESHOOTING:${'\x1b[0m'} ${'\x1b[1m'}Problem:${'\x1b[0m'} Git is not installed ${'\x1b[1m'}Solution:${'\x1b[0m'} Install Git from https://git-scm.com/ ${'\x1b[1m'}Problem:${'\x1b[0m'} Not a git repository ${'\x1b[1m'}Solution:${'\x1b[0m'} Run 'git init' to initialize a repository ${'\x1b[1m'}Problem:${'\x1b[0m'} No git remote found ${'\x1b[1m'}Solution:${'\x1b[0m'} Run 'git remote add origin <url>' to add a remote ${'\x1b[1m'}Problem:${'\x1b[0m'} Authentication failed ${'\x1b[1m'}Solution:${'\x1b[0m'} Configure Git credentials: - Use SSH keys: https://docs.github.com/en/authentication - Or configure credential helper: git config credential.helper store - Or use personal access token ${'\x1b[1m'}Problem:${'\x1b[0m'} Push rejected due to conflicts ${'\x1b[1m'}Solution:${'\x1b[0m'} Use --force flag to force push (overwrites remote) ${'\x1b[2m'}projection deploy --force${'\x1b[0m'} ${'\x1b[33m'}⚠️ Warning: This will overwrite the remote branch history${'\x1b[0m'} ${'\x1b[1m'}Problem:${'\x1b[0m'} Build fails during deployment ${'\x1b[1m'}Solution:${'\x1b[0m'} Fix build errors first, or use --no-build to deploy existing files ${'\x1b[2m'}projection build${'\x1b[0m'} (to test build separately) ${'\x1b[2m'}projection deploy --no-build${'\x1b[0m'} (to deploy without building) ${'\x1b[1m'}Problem:${'\x1b[0m'} Site shows 404 or broken links ${'\x1b[1m'}Solution:${'\x1b[0m'} Check that baseUrl in config matches your repository name For https://username.github.io/repo/, use baseUrl: "/repo/" ${'\x1b[1m'}Problem:${'\x1b[0m'} Site not updating after deployment ${'\x1b[1m'}Solution:${'\x1b[0m'} GitHub Pages can take a few minutes to update - Check repository Settings → Pages for deployment status - Clear browser cache and try again - Verify gh-pages branch has the latest changes ${'\x1b[1m'}Problem:${'\x1b[0m'} Custom domain not working ${'\x1b[1m'}Solution:${'\x1b[0m'} Ensure homepage is set in config and DNS is configured: 1. Add homepage: "yourdomain.com" to projection.config.json 2. Configure DNS records with your domain provider 3. Enable HTTPS in GitHub Pages settings ${'\x1b[1m'}WORKFLOW:${'\x1b[0m'} The deploy command performs these steps: 1. ${'\x1b[2m'}Validates Git installation and repository setup${'\x1b[0m'} 2. ${'\x1b[2m'}Checks for projects file${'\x1b[0m'} 3. ${'\x1b[2m'}Loads deployment configuration${'\x1b[0m'} 4. ${'\x1b[2m'}Builds the site (unless --no-build)${'\x1b[0m'} 5. ${'\x1b[2m'}Adds .nojekyll file to disable Jekyll${'\x1b[0m'} 6. ${'\x1b[2m'}Adds CNAME file if custom domain configured${'\x1b[0m'} 7. ${'\x1b[2m'}Pushes to gh-pages branch${'\x1b[0m'} 8. ${'\x1b[2m'}Displays deployment URL and instructions${'\x1b[0m'} ${'\x1b[1m'}MORE INFORMATION:${'\x1b[0m'} GitHub Pages Documentation: https://docs.github.com/en/pages Projection Documentation: https://github.com/quasarbright/projection `); } /** * Deploy command - builds and deploys the portfolio site to GitHub Pages * * @param options - Deployment options from command-line * @throws ProjectionError if validation fails or deployment cannot proceed */ async function deploy(options = {}) { // Check if help flag is set if (options.help) { showDeployHelp(); return; } const cwd = process.cwd(); try { logger_1.Logger.header('🚀 Deploying to GitHub Pages'); logger_1.Logger.newline(); // Step 1: Validate Git installation logger_1.Logger.step('Checking Git installation...'); const gitInstalled = await git_helper_1.GitHelper.isGitInstalled(); if (!gitInstalled) { throw new errors_1.ProjectionError('Git is not installed or not in PATH', errors_1.ErrorCodes.VALIDATION_ERROR, { solution: 'Install Git from https://git-scm.com/', required: 'Git is required for deployment to GitHub Pages' }); } logger_1.Logger.success('Git is installed'); // Step 2: Validate Git repository and remote configuration logger_1.Logger.step('Validating Git repository...'); const remote = options.remote || 'origin'; const validation = await git_helper_1.GitHelper.validateRepository(cwd, remote); if (!validation.isGitRepo) { throw new errors_1.ProjectionError('Not a git repository', errors_1.ErrorCodes.VALIDATION_ERROR, { solution: "Run 'git init' to initialize a repository", required: 'A Git repository is required for deployment' }); } if (!validation.hasRemote) { throw new errors_1.ProjectionError(`No git remote '${remote}' found`, errors_1.ErrorCodes.VALIDATION_ERROR, { solution: `Run 'git remote add ${remote} <url>' to add a remote`, required: 'A Git remote is required to push the deployment' }); } logger_1.Logger.success(`Git repository validated (remote: ${remote})`); // Step 3: Verify projects data file exists logger_1.Logger.step('Checking for projects file...'); const projectFile = project_file_finder_1.ProjectFileFinder.find(cwd); if (!projectFile) { const possibleFiles = project_file_finder_1.ProjectFileFinder.getSupportedFileNames(); throw new errors_1.ProjectionError('No projects file found', errors_1.ErrorCodes.FILE_NOT_FOUND, { solution: `Create one of: ${possibleFiles.join(', ')}`, required: 'A projects file is required to build the site' }); } logger_1.Logger.success(`Found projects file: ${projectFile.path}`); // Step 4: Load deployment configuration logger_1.Logger.step('Loading deployment configuration...'); const config = await deployment_config_1.DeploymentConfigLoader.load(cwd, options); logger_1.Logger.success('Configuration loaded'); // Step 5: Display pre-deployment summary logger_1.Logger.newline(); logger_1.Logger.header('📋 Deployment Summary'); logger_1.Logger.keyValue('Repository', validation.remoteUrl); logger_1.Logger.keyValue('Branch', config.branch); logger_1.Logger.keyValue('Build Directory', config.buildDir); logger_1.Logger.keyValue('Base URL', config.baseUrl); if (config.homepage) { logger_1.Logger.keyValue('Custom Domain', config.homepage); } if (options.dryRun) { logger_1.Logger.keyValue('Mode', 'DRY RUN (no changes will be made)'); } logger_1.Logger.newline(); // If dry-run, stop here if (options.dryRun) { logger_1.Logger.info('Dry run complete. No deployment was performed.'); return; } // Step 6: Build the site (unless --no-build flag is set) if (!options.noBuild) { logger_1.Logger.newline(); logger_1.Logger.header('🔨 Building site'); logger_1.Logger.newline(); try { // Use shared build helper await build_helper_1.BuildHelper.runBuild({ cwd, outputDir: config.buildDir, clean: true }); } catch (error) { logger_1.Logger.newline(); logger_1.Logger.error('Deployment aborted due to build failure'); logger_1.Logger.newline(); throw new errors_1.ProjectionError('Build failed', errors_1.ErrorCodes.RUNTIME_ERROR, { originalError: error.message }); } } else { logger_1.Logger.newline(); logger_1.Logger.info('Skipping build (--no-build flag set)'); logger_1.Logger.dim(`Using existing files in ${config.buildDir}`); logger_1.Logger.newline(); } // Step 7: Configure and execute gh-pages deployment logger_1.Logger.newline(); logger_1.Logger.header('📦 Deploying to GitHub Pages'); logger_1.Logger.newline(); try { // Prepare build directory path const buildDirPath = path.isAbsolute(config.buildDir) ? config.buildDir : path.join(cwd, config.buildDir); // Verify build directory exists if (!fs.existsSync(buildDirPath)) { throw new errors_1.ProjectionError(`Build directory not found: ${buildDirPath}`, errors_1.ErrorCodes.FILE_NOT_FOUND, { solution: 'Run the build first or check the --dir option', required: 'The build directory must exist before deployment' }); } // Add .nojekyll file to disable Jekyll processing const nojekyllPath = path.join(buildDirPath, '.nojekyll'); if (!fs.existsSync(nojekyllPath)) { logger_1.Logger.step('Adding .nojekyll file...'); fs.writeFileSync(nojekyllPath, '', 'utf8'); logger_1.Logger.success('.nojekyll file added'); } // Add CNAME file if homepage is configured if (config.homepage) { const cnamePath = path.join(buildDirPath, 'CNAME'); logger_1.Logger.step(`Adding CNAME file for ${config.homepage}...`); fs.writeFileSync(cnamePath, config.homepage, 'utf8'); logger_1.Logger.success('CNAME file added'); } // Configure gh-pages options const ghPagesOptions = { branch: config.branch, dest: '.', message: options.message || `Deploy to GitHub Pages - ${new Date().toISOString()}`, remote: config.remote, dotfiles: true, // Include .nojekyll add: true, // Preserve commit history ...(options.force && { force: true }) // Force push if requested }; logger_1.Logger.step('Publishing to GitHub Pages...'); logger_1.Logger.dim(`Branch: ${config.branch}`); logger_1.Logger.dim(`Remote: ${config.remote}`); logger_1.Logger.newline(); // Publish to gh-pages await new Promise((resolve, reject) => { ghpages.publish(buildDirPath, ghPagesOptions, (err) => { if (err) { reject(err); } else { resolve(); } }); }); // Display success message logger_1.Logger.newline(); logger_1.Logger.icon('🎉', 'Deployment successful!', '\x1b[32m'); logger_1.Logger.newline(); // Generate GitHub Pages URL const repoName = deployment_config_1.DeploymentConfigLoader.extractRepoName(config.repositoryUrl); const githubPagesUrl = config.homepage || deployment_config_1.DeploymentConfigLoader.generateGitHubPagesUrl(config.repositoryUrl); logger_1.Logger.keyValue('Site URL', githubPagesUrl); logger_1.Logger.keyValue('Branch', config.branch); if (ghPagesOptions.message) { logger_1.Logger.keyValue('Commit', ghPagesOptions.message); } logger_1.Logger.newline(); logger_1.Logger.info('Your site has been deployed to GitHub Pages!'); logger_1.Logger.dim('Note: It may take a few minutes for GitHub Pages to update.'); logger_1.Logger.newline(); // Provide instructions for enabling GitHub Pages if needed logger_1.Logger.info('If this is your first deployment:'); logger_1.Logger.dim('1. Go to your repository settings on GitHub'); logger_1.Logger.dim(`2. Navigate to Pages section`); logger_1.Logger.dim(`3. Ensure the source is set to the '${config.branch}' branch`); logger_1.Logger.newline(); } catch (error) { logger_1.Logger.newline(); logger_1.Logger.error('Deployment failed'); logger_1.Logger.newline(); // Handle gh-pages specific errors if (error instanceof Error) { const errorMessage = error.message.toLowerCase(); if (errorMessage.includes('permission denied') || errorMessage.includes('authentication')) { logger_1.Logger.dim('Authentication failed. Please check your Git credentials.'); logger_1.Logger.newline(); logger_1.Logger.info('Solutions:'); logger_1.Logger.dim('• Configure Git credentials: git config credential.helper store'); logger_1.Logger.dim('• Use SSH keys: https://docs.github.com/en/authentication/connecting-to-github-with-ssh'); logger_1.Logger.dim('• Check repository permissions'); } else if (errorMessage.includes('rejected') || errorMessage.includes('conflict')) { logger_1.Logger.dim('Push rejected due to conflicts.'); logger_1.Logger.newline(); logger_1.Logger.info('Solutions:'); logger_1.Logger.dim('• Use --force flag to force push (caution: overwrites remote)'); logger_1.Logger.dim('• Manually resolve conflicts in the gh-pages branch'); } else if (errorMessage.includes('not found') || errorMessage.includes('does not exist')) { logger_1.Logger.dim('Repository or remote not found.'); logger_1.Logger.newline(); logger_1.Logger.info('Solutions:'); logger_1.Logger.dim('• Verify the remote URL: git remote -v'); logger_1.Logger.dim('• Check repository permissions'); logger_1.Logger.dim('• Ensure the repository exists on GitHub'); } else { logger_1.Logger.dim(error.message); } logger_1.Logger.newline(); } throw new errors_1.ProjectionError('Deployment failed', errors_1.ErrorCodes.RUNTIME_ERROR, { originalError: error.message }); } } catch (error) { if (error instanceof errors_1.ProjectionError) { logger_1.Logger.error(error.message); logger_1.Logger.newline(); if (error.details.solution) { logger_1.Logger.info(`Solution: ${error.details.solution}`); } if (error.details.required) { logger_1.Logger.dim(error.details.required); } throw error; } // Re-throw unexpected errors throw error; } } //# sourceMappingURL=deploy.js.map