| @@ -1,5 +1,5 @@ |
| 1 | | import { execFileSync } from "child_process"; |
| 2 | 1 | import { mkdirSync, rmSync, existsSync, renameSync } from "fs"; |
| 2 | import { execFileSync } from "child_process"; |
| 3 | 3 | import type Database from "better-sqlite3"; |
| 4 | 4 | |
| 5 | 5 | export class PagesDeployer { |
| @@ -20,15 +20,15 @@ |
| 20 | 20 | * Custom domain: sitesDir/{domain}/ (Caddy serves via {host} matching) |
| 21 | 21 | * Default: sitesDir/_pages/{owner}/{repo}/ (Caddy serves via path-based routing on pages.grove.host) |
| 22 | 22 | */ |
| 23 | | getDeployPath(repoName: string): { path: string; isCustomDomain: boolean } | null { |
| 23 | getDeployPath(ownerName: string, repoName: string): { path: string; isCustomDomain: boolean } | null { |
| 24 | 24 | const repo = this.db |
| 25 | 25 | .prepare( |
| 26 | 26 | `SELECT r.pages_domain, r.pages_enabled, rwo.owner_name, r.name |
| 27 | 27 | FROM repos r |
| 28 | 28 | JOIN repos_with_owner rwo ON rwo.id = r.id |
| 29 | | WHERE r.name = ? AND r.pages_enabled = 1` |
| 29 | WHERE rwo.owner_name = ? AND r.name = ? AND r.pages_enabled = 1` |
| 30 | 30 | ) |
| 31 | | .get(repoName) as any; |
| 31 | .get(ownerName, repoName) as any; |
| 32 | 32 | |
| 33 | 33 | if (!repo) return null; |
| 34 | 34 | |
| @@ -41,29 +41,27 @@ |
| 41 | 41 | |
| 42 | 42 | /** |
| 43 | 43 | * Deploy a repo's pages content to disk. |
| 44 | | * Archives the repo from bridge and extracts to the deploy path. |
| 44 | * Fetches a tar archive from the bridge and extracts to the deploy path. |
| 45 | 45 | * Uses atomic rename so Caddy never sees a half-written site. |
| 46 | 46 | */ |
| 47 | | async deploy(repoName: string, ref: string): Promise<void> { |
| 48 | | const deployInfo = this.getDeployPath(repoName); |
| 47 | async deploy(ownerName: string, repoName: string, ref: string): Promise<void> { |
| 48 | const deployInfo = this.getDeployPath(ownerName, repoName); |
| 49 | 49 | if (!deployInfo) return; |
| 50 | 50 | |
| 51 | 51 | const { path: destDir } = deployInfo; |
| 52 | 52 | |
| 53 | | this.logger.info({ repo: repoName, dest: destDir, ref }, "Deploying pages"); |
| 53 | this.logger.info({ owner: ownerName, repo: repoName, dest: destDir, ref }, "Deploying pages"); |
| 54 | 54 | |
| 55 | 55 | const tmpDir = `${destDir}.tmp-${Date.now()}`; |
| 56 | 56 | |
| 57 | 57 | try { |
| 58 | mkdirSync(tmpDir, { recursive: true }); |
| 59 | |
| 58 | 60 | const url = `${this.bridgeUrl}/repos/${repoName}/archive/${ref}`; |
| 59 | 61 | const res = await fetch(url); |
| 60 | 62 | if (!res.ok) { |
| 61 | | throw new Error( |
| 62 | | `Archive fetch failed: ${res.status} ${res.statusText}` |
| 63 | | ); |
| 63 | throw new Error(`Archive fetch failed: ${res.status} ${res.statusText}`); |
| 64 | 64 | } |
| 65 | | |
| 66 | | mkdirSync(tmpDir, { recursive: true }); |
| 67 | 65 | const tarData = Buffer.from(await res.arrayBuffer()); |
| 68 | 66 | execFileSync("tar", ["xf", "-", "-C", tmpDir], { input: tarData }); |
| 69 | 67 | |
| @@ -80,7 +78,7 @@ |
| 80 | 78 | } |
| 81 | 79 | |
| 82 | 80 | this.logger.info( |
| 83 | | { repo: repoName, dest: destDir }, |
| 81 | { owner: ownerName, repo: repoName, dest: destDir }, |
| 84 | 82 | "Pages deployed successfully" |
| 85 | 83 | ); |
| 86 | 84 | } catch (err) { |
| 87 | 85 | |