All files / backend/dist/sync/git git-sync.js

0% Statements 0/54
0% Branches 0/36
0% Functions 0/6
0% Lines 0/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                                                                                                                                                                                                                                                 
/**
 * Git Sync Service
 *
 * Universal git-based sync that works with any git provider (GitHub, GitLab, Forgejo, etc.).
 * Handles git operations and delegates CI configuration to provider-specific helpers.
 */
import path from 'path';
import fs from 'fs-extra';
import { EmbgitSyncBase } from '../embgit-sync-base.js';
import { getCIConfigurator } from '../ci-configurators/index.js';
/**
 * GitSync - Universal git-based sync for any provider
 */
export class GitSync extends EmbgitSyncBase {
    config;
    ciConfigurator;
    constructor(config, siteKey, dependencies) {
        // Pass config to parent - it will be cast to BaseSyncConfig
        super(config, siteKey, dependencies);
        this.config = config;
        this.ciConfigurator = getCIConfigurator(this.config.gitProvider);
    }
    /**
     * Build a Git URL from configuration
     */
    getGitUrl() {
        return this.buildGitUrl(this.config.gitBaseUrl, this.config.username, this.config.repository, this.config.gitProtocol, this.config.sshPort);
    }
    /**
     * Build a Git URL from components
     * @param baseUrl - The Git host, may include port for HTTPS (e.g., 'github.com', 'localhost:3000')
     * @param org - The organization or username
     * @param repo - The repository name
     * @param protocol - 'ssh' or 'https' (defaults to 'ssh')
     * @param sshPort - SSH port (defaults to 22, only used for SSH protocol)
     */
    buildGitUrl(baseUrl, org, repo, protocol = 'ssh', sshPort = 22) {
        // Ensure repo ends with .git
        const repoWithGit = repo.endsWith('.git') ? repo : `${repo}.git`;
        if (protocol === 'ssh') {
            // Extract host without port for SSH (port is specified separately)
            const host = baseUrl.split(':')[0];
            if (sshPort === 22) {
                // Standard SSH format: git@host:org/repo.git
                return `git@${host}:${org}/${repoWithGit}`;
            }
            else {
                // Non-standard port SSH format: ssh://git@host:port/org/repo.git
                return `ssh://git@${host}:${sshPort}/${org}/${repoWithGit}`;
            }
        }
        else {
            // HTTPS format: https://host:port/org/repo.git
            // Use http for localhost, https otherwise
            const isLocalhost = baseUrl.startsWith('localhost') || baseUrl.startsWith('127.0.0.1');
            const scheme = isLocalhost ? 'http' : 'https';
            return `${scheme}://${baseUrl}/${org}/${repoWithGit}`;
        }
    }
    /**
     * Get the log prefix for console output
     */
    getLogPrefix() {
        return `GIT[${this.config.gitProvider.toUpperCase()}]`;
    }
    /**
     * Override step 2 for source scope to use provider-specific CI
     */
    async publishStep2PrepareDircontentsSource(fullDestinationPath) {
        if (!this.fromPath) {
            throw new Error('Last build directory is not set');
        }
        await this.syncSourceToDestination(this.fromPath, fullDestinationPath, 'all');
        // Use provider-specific CI configuration
        if (this.config.publishScope === 'source' && this.config.setCIWorkflow && this.ciConfigurator) {
            await this.ciConfigurator.writeWorkflow(fullDestinationPath, {
                branch: this.config.branch || 'main',
                overrideBaseURL: this.config.overrideBaseURLSwitch ? this.config.overrideBaseURL : undefined,
            });
        }
        if (this.config.CNAMESwitch && this.config.CNAME) {
            await this.githubCname(fullDestinationPath);
        }
        await fs.ensureDir(path.join(fullDestinationPath, 'static'));
        this.outputConsole.appendLine('prepare and sync finished');
        return true;
    }
    /**
     * Override hard push to use provider-specific CI
     */
    async hardPush() {
        const tmpDir = this.pathHelper.getTempDir();
        await this.ensureSyncDirEmpty(tmpDir);
        const tmpCloneDir = path.join(tmpDir, 'tmpclone');
        await fs.mkdir(tmpCloneDir);
        const tmpKeypathPrivate = await this.tempCreatePrivateKey();
        const parentPath = path.join(this.pathHelper.getRoot(), 'sites', this.siteKey, 'githubSyncRepo');
        await this.ensureSyncDirEmpty(parentPath);
        this.outputConsole.appendLine(`START ${this.getLogPrefix()} CHECKOUT`);
        this.outputConsole.appendLine('-----------------');
        this.outputConsole.appendLine('  git url:             ' + this.getGitUrl());
        this.outputConsole.appendLine('  private key path:    ' + tmpKeypathPrivate);
        this.outputConsole.appendLine('  destination path:    ' + this.fullDestinationPath());
        this.outputConsole.appendLine('');
        this.outputConsole.appendLine('  repository:          ' + this.config.repository);
        this.outputConsole.appendLine('  branch:              ' + this.config.branch);
        this.outputConsole.appendLine('  email:               ' + this.config.email);
        this.outputConsole.appendLine('-----------------');
        this.outputConsole.appendLine('');
        this.sendProgress('Getting latest remote commit history..', 20);
        await this.embgit.clonePrivateWithKey(this.getGitUrl(), tmpCloneDir, this.config.deployPrivateKey);
        this.sendProgress('Copying commit history to destination directory', 30);
        await fs.copy(path.join(tmpCloneDir, '.git'), path.join(this.fullDestinationPath(), '.git'));
        this.sendProgress('Copying site files to git destination directory', 40);
        const currentSitePath = await this.getCurrentSitePath();
        const filter = this.createIgnoreFilter(currentSitePath);
        await fs.copy(currentSitePath, this.fullDestinationPath(), { filter });
        // Use provider-specific CI configuration
        if (this.config.publishScope === 'source' && this.config.setCIWorkflow && this.ciConfigurator) {
            await this.ciConfigurator.writeWorkflow(this.fullDestinationPath(), {
                branch: this.config.branch || 'main',
                overrideBaseURL: this.config.overrideBaseURLSwitch ? this.config.overrideBaseURL : undefined,
            });
        }
        await this.publishStep3AddCommitPush(tmpKeypathPrivate, this.fullDestinationPath());
        return true;
    }
}