File size: 4,962 Bytes
391c43e | 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 130 131 132 133 134 135 136 137 138 139 | /**
* Pure helpers for deployment URL/asset-path resolution.
*
* Kept separate from static-builder.ts so they carry no server-only / filesystem
* dependencies and can be unit-tested directly.
*/
import { VirtualFile } from '@/lib/vfs/types';
export interface DeploymentServing {
/** True when the deployment is served at a root (needs root-relative asset paths). */
servedAtRoot: boolean;
/** Base URL used for SEO (sitemap, canonical, Open Graph). */
baseUrl: string;
}
/**
* Decide how a deployment is served, which controls its asset path style and
* SEO URLs:
* - custom domain → served at that domain's root (root-relative paths)
* - static proxy (Caddy subdomains) active AND a slug → served at the slug
* subdomain root (root-relative paths)
* - otherwise → served under /deployments/{id}/ by the Next route (prefixed
* paths). A slug alone doesn't imply root serving, since publish always
* assigns one.
*/
export function resolveDeploymentServing(
deployment: { slug?: string; customDomain?: string },
deploymentId: string,
opts: { staticProxyEnabled: boolean; appUrl: string }
): DeploymentServing {
const servedViaSubdomain = opts.staticProxyEnabled && !!deployment.slug;
const servedAtRoot = !!deployment.customDomain || servedViaSubdomain;
const instanceDomain = opts.appUrl.replace(/^https?:\/\//, '');
const baseUrl = deployment.customDomain
? `https://${deployment.customDomain}`
: servedViaSubdomain
? `https://${deployment.slug}.${instanceDomain}`
: `${opts.appUrl}/deployments/${deploymentId}`;
return { servedAtRoot, baseUrl };
}
/**
* Replace both blob URLs and file path references with deployment-prefixed absolute paths
*/
export function replaceAssetPathsWithDeploymentPrefix(
content: string,
blobUrlToPath: Map<string, string>,
allFiles: VirtualFile[],
deploymentId: string,
servedAtRoot?: boolean
): string {
let result = content;
// If served at domain root (custom domain or subdomain slug), use root-relative paths.
// Otherwise use deployment-prefixed paths for direct /deployments/{id}/ access.
const pathPrefix = servedAtRoot ? '' : `/deployments/${deploymentId}`;
// First, replace all blob URLs with appropriate paths
for (const [blobUrl, filePath] of blobUrlToPath) {
const absolutePath = `${pathPrefix}${filePath}`;
result = result.replace(new RegExp(escapeRegex(blobUrl), 'g'), absolutePath);
}
// Helper to check if path is already prefixed with deployment path
const isAlreadyPrefixed = (path: string) =>
pathPrefix && path.startsWith(pathPrefix);
// Rewrite all internal absolute paths for HTML files
// Pattern matches: href="/anything.html" or href="/anything.htm"
result = result.replace(
/href=(["'])(\/[^"']*\.html?)\1/g,
(match, quote, filePath) => {
if (isAlreadyPrefixed(filePath)) {
return match;
}
return `href=${quote}${pathPrefix}${filePath}${quote}`;
}
);
// Rewrite asset directory paths (styles, scripts, assets, images, fonts, js, css)
const assetDirPattern = /(?:href|src)=(["'])(\/(?:styles|scripts|assets|images|fonts|js|css)\/[^"']+)\1/g;
result = result.replace(assetDirPattern, (match, quote, filePath) => {
if (isAlreadyPrefixed(filePath)) {
return match;
}
return match.replace(filePath, `${pathPrefix}${filePath}`);
});
// Rewrite root-level asset references (e.g., /bundle.js, /bundle.css, /favicon.ico)
const rootAssetPattern = /(?:href|src)=(["'])(\/[^"'\/]+\.(?:js|css|json|xml|ico|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|eot))\1/g;
result = result.replace(rootAssetPattern, (match, quote, filePath) => {
if (isAlreadyPrefixed(filePath)) {
return match;
}
return match.replace(filePath, `${pathPrefix}${filePath}`);
});
// Rewrite CSS url() references for asset directories
result = result.replace(
/url\(['"]?(\/(?:styles|scripts|assets|images|fonts|js|css)\/[^'")]+)['"]?\)/g,
(match, filePath) => {
if (isAlreadyPrefixed(filePath)) {
return match;
}
return match.replace(filePath, `${pathPrefix}${filePath}`);
}
);
// Handle relative HTML paths (e.g., href="about.html") - convert to absolute with prefix
result = result.replace(
/href=(["'])([^"':/][^"']*\.html?)\1/g,
(match, quote, filePath) => {
// Skip if it looks like an already-processed path or external
if (filePath.startsWith('/') || filePath.includes('://')) {
return match;
}
return `href=${quote}${pathPrefix}/${filePath}${quote}`;
}
);
// Handle root path href="/" - rewrite to deployment prefix
if (pathPrefix) {
result = result.replace(
/href=(["'])\/\1/g,
(match, quote) => `href=${quote}${pathPrefix}/${quote}`
);
}
return result;
}
/**
* Escape special regex characters
*/
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
|