/s', '', $html ); $html = preg_replace( '/\sdata-lovable-[a-z0-9-]+=(["\']).*?\1/i', '', $html ); // Any whose content references lovable.dev (og:image, // twitter:image, ...). $html = preg_replace( '/]*content=(["\'])[^"\']*lovable\.dev[^"\']*\1[^>]*>\s*/i', '', $html ); // The default twitter:site meta, regardless of attribute order. $html = preg_replace( '/]*\btwitter:site\b)(?=[^>]*content=["\'][^"\']*lovable[^"\']*["\'])[^>]*>\s*/i', '', $html ); // The default author meta (content="Lovable"). $html = preg_replace( '/]*\bname=["\']author["\'])(?=[^>]*\bcontent=["\']lovable["\'])[^>]*>\s*/i', '', $html ); // The default "Lovable Generated Project" description — Lovable's // own unedited placeholder text, left behind whenever a project // owner never customized their SEO description. Matched by exact // content, regardless of which meta name/property carries it // (description, og:description, twitter:description all use it). $html = preg_replace( '/]*\bcontent=["\']lovable generated project["\'])[^>]*>\s*/i', '', $html ); return $html; } /** * {@inheritDoc} * * Flags Supabase — Lovable's default backend — and, generically, any * other hardcoded `/api/...` backend dependency, when a bundled JS * file references one. Reported rather than silently dropped: no * server-backed functionality (auth, database, live data) survives * conversion regardless of what backend it talked to. * * Also flags Supabase Realtime's `postgres_changes` subscription API * specifically — a genuinely distinctive literal string (it's the * event-type constant `.on()` is called with), so this is narrow and * low-false-positive like every other marker here. Verified against * a real export (and a from-scratch rebuild of its own raw source, * using the same package manager its lockfile specifies) that a * component calling `.channel(name).on("postgres_changes", ...) * .subscribe()` from more than one place in the app, with the same * hardcoded channel name, throws an uncaught error the moment both * mount — Supabase's client reuses a channel object by name, and * throws if `.on()` is called again on one that's already * `.subscribe()`d. That's a bug in the export's own code, not * something this conversion can fix (Esipher never touches bundled * JS logic) — but it's worth surfacing, since its symptom (the page * crashing to blank, often specifically after logging in) gives no * indication at all that Realtime is the cause. * * @param string $dir Absolute path to the extracted export root. * * @return array */ public function get_metadata( $dir ) { return array( 'source' => 'lovable', 'supabase_detected' => $this->bundled_js_contains( $dir, 'supabase' ), 'backend_api_detected' => $this->bundled_js_contains( $dir, '/api/' ), 'realtime_detected' => $this->bundled_js_contains( $dir, 'postgres_changes' ), ); } }