import fs from 'node:fs/promises'; import path from 'node:path'; import { isRelativeDownloadReference, resolveWorkspaceRelativeFilePath, splitReferenceParts, } from './mindspace-html-download-links.mjs'; import { workspaceThumbnailRelativePath } from './mindspace-workspace-thumbnails.mjs'; const PRIVATE_ASSET_URL_PATTERN = /(?:https?:\/\/[^/]+)?\/api\/mindspace\/v1\/assets\/([a-z0-9-]+)\/download(?:\?[^"'<>\\\s)]*)?/gi; const URL_ATTR_PATTERN = /(\b(?:href|src)\s*=\s*["'])([^"']+)(["'])/gi; export function extractAssetIdsFromHtml(html) { return [ ...new Set( [...String(html ?? '').matchAll(PRIVATE_ASSET_URL_PATTERN)] .map((match) => match[1]) .filter(Boolean), ), ]; } export function extractRelativeDownloadRefs(html) { const refs = new Set(); for (const match of String(html ?? '').matchAll(URL_ATTR_PATTERN)) { const url = match[2]; if (isRelativeDownloadReference(url)) { refs.add(splitReferenceParts(url).pathPart); } } return [...refs]; } export function collectWorkspacePurgeTargets(htmlRelativePath, html) { const targets = new Set(); const normalizedHtmlPath = String(htmlRelativePath ?? '').replace(/^\/+/, ''); if (normalizedHtmlPath) { targets.add(normalizedHtmlPath); if (normalizedHtmlPath.toLowerCase().endsWith('.html')) { targets.add(workspaceThumbnailRelativePath(normalizedHtmlPath)); const pngThumb = normalizedHtmlPath.replace(/\.html$/i, '.thumbnail.png'); if (pngThumb !== normalizedHtmlPath) targets.add(pngThumb); } } for (const ref of extractRelativeDownloadRefs(html)) { targets.add(resolveWorkspaceRelativeFilePath(normalizedHtmlPath || 'public/index.html', ref)); } return [...targets].filter(Boolean); } export async function purgeWorkspacePageArtifacts({ publishDir, htmlRelativePath = null, html = '', } = {}) { if (!publishDir) return { removed: [], skipped: [] }; const resolvedRoot = path.resolve(publishDir); const removed = []; const skipped = []; for (const relativeTarget of collectWorkspacePurgeTargets(htmlRelativePath, html)) { const absolutePath = path.resolve(publishDir, ...relativeTarget.split('/')); if ( absolutePath !== resolvedRoot && !absolutePath.startsWith(`${resolvedRoot}${path.sep}`) ) { skipped.push(relativeTarget); continue; } try { await fs.unlink(absolutePath); removed.push(relativeTarget); } catch (error) { if (error?.code !== 'ENOENT') skipped.push(relativeTarget); } } return { removed, skipped }; } export const pagePurgeInternals = { extractAssetIdsFromHtml, extractRelativeDownloadRefs, collectWorkspacePurgeTargets, };