Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
One-time setup that gathers your project's design context and saves it to CLAUDE.md for future sessions.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/live-sveltekit-adapter.mjs
1/**2* SvelteKit live-mode adapter.3*4* SvelteKit must not be patched through src/app.html. That file is a document5* template, not framework-owned component chrome. The adapter keeps SvelteKit6* work limited to mounting a dev-only shadow host from +layout.svelte; the7* actual live UI remains the shared plain-DOM browser chrome.8*/910import fs from 'node:fs';11import path from 'node:path';1213export const SVELTE_LIVE_ROOT_COMPONENT = 'src/lib/impeccable/ImpeccableLiveRoot.svelte';14export const SVELTE_LAYOUT_MARKER_OPEN = '<!-- impeccable-live-svelte-start -->';15export const SVELTE_LAYOUT_MARKER_CLOSE = '<!-- impeccable-live-svelte-end -->';16export const SVELTE_ROOT_IMPORT = "import ImpeccableLiveRoot from '$lib/impeccable/ImpeccableLiveRoot.svelte';";1718export function detectSvelteKitProject(cwd = process.cwd(), config = null) {19const appHtml = findSvelteKitAppHtml(cwd, config);20if (!appHtml) return null;21const hasTemplateMarkers = fileIncludes(path.join(cwd, appHtml), '%sveltekit.body%')22&& fileIncludes(path.join(cwd, appHtml), '%sveltekit.head%');23if (!hasTemplateMarkers) return null;2425const hasSvelteConfig = fs.existsSync(path.join(cwd, 'svelte.config.js'))26|| fs.existsSync(path.join(cwd, 'svelte.config.mjs'))27|| fs.existsSync(path.join(cwd, 'svelte.config.cjs'))28|| fs.existsSync(path.join(cwd, 'svelte.config.ts'));29const hasKitPackage = packageHasSvelteKit(cwd);30if (!hasSvelteConfig && !hasKitPackage) return null;3132return {33appHtml,34layoutFile: findSvelteKitLayout(cwd),35rootComponent: SVELTE_LIVE_ROOT_COMPONENT,36};37}3839export function applySvelteKitLiveAdapter({ cwd = process.cwd(), port, config = null } = {}) {40if (!Number.isFinite(Number(port))) {41throw new Error('SvelteKit live adapter requires a numeric port');42}43const detected = detectSvelteKitProject(cwd, config);44if (!detected) return null;4546ensureSvelteLiveRootComponent(cwd, Number(port));4748const layoutRel = detected.layoutFile;49const layoutAbs = path.join(cwd, layoutRel);50fs.mkdirSync(path.dirname(layoutAbs), { recursive: true });51const layoutExisted = fs.existsSync(layoutAbs);52const before = layoutExisted ? fs.readFileSync(layoutAbs, 'utf-8') : defaultSvelteLayout();53const after = patchSvelteLayout(before);54fs.writeFileSync(layoutAbs, after, 'utf-8');5556return {57file: layoutRel,58adapter: 'sveltekit',59inserted: after !== before || !layoutExisted,60appHtmlUntouched: true,61rootComponent: SVELTE_LIVE_ROOT_COMPONENT,62};63}6465export function removeSvelteKitLiveAdapter({ cwd = process.cwd(), config = null } = {}) {66const detected = detectSvelteKitProject(cwd, config);67if (!detected) return null;6869const layoutAbs = path.join(cwd, detected.layoutFile);70let removed = false;71if (fs.existsSync(layoutAbs)) {72const before = fs.readFileSync(layoutAbs, 'utf-8');73const after = unpatchSvelteLayout(before);74if (after !== before) {75fs.writeFileSync(layoutAbs, after, 'utf-8');76removed = true;77}78}7980const rootAbs = path.join(cwd, SVELTE_LIVE_ROOT_COMPONENT);81if (fs.existsSync(rootAbs)) {82fs.rmSync(rootAbs, { force: true });83removed = true;84}8586pruneEmptyDir(path.dirname(rootAbs), path.join(cwd, 'src'));8788return {89file: detected.layoutFile,90adapter: 'sveltekit',91removed,92appHtmlUntouched: true,93rootComponent: SVELTE_LIVE_ROOT_COMPONENT,94};95}9697export function patchSvelteLayout(content) {98let out = String(content || '');99if (!out.includes(SVELTE_ROOT_IMPORT)) {100const scriptMatch = out.match(/<script(?:\s[^>]*)?>/i);101if (scriptMatch) {102const insertAt = scriptMatch.index + scriptMatch[0].length;103out = out.slice(0, insertAt) + '\n ' + SVELTE_ROOT_IMPORT + out.slice(insertAt);104} else {105out = `<script>\n ${SVELTE_ROOT_IMPORT}\n</script>\n\n` + out;106}107}108109if (!out.includes(SVELTE_LAYOUT_MARKER_OPEN)) {110const block = `${SVELTE_LAYOUT_MARKER_OPEN}\n<ImpeccableLiveRoot />\n${SVELTE_LAYOUT_MARKER_CLOSE}\n`;111const renderMatch = out.match(/\{@render\s+children(?:\?\.)?\(\)\s*\}/);112const slotMatch = out.match(/<slot\s*\/?>/);113const match = renderMatch || slotMatch;114if (match) {115out = out.slice(0, match.index) + block + out.slice(match.index);116} else {117out = out.replace(/\s*$/, '\n\n' + block);118}119}120121return out;122}123124export function unpatchSvelteLayout(content) {125let out = String(content || '');126const blockRe = new RegExp(127'([ \\t]*)' + escapeRegExp(SVELTE_LAYOUT_MARKER_OPEN)128+ '\\n<ImpeccableLiveRoot\\s*/>\\n'129+ escapeRegExp(SVELTE_LAYOUT_MARKER_CLOSE)130+ '\\n?',131'g',132);133out = out.replace(blockRe, '$1');134out = out.replace(new RegExp('^\\s*' + escapeRegExp(SVELTE_ROOT_IMPORT) + '\\s*\\n?', 'gm'), '');135out = out.replace(/<script>\s*<\/script>\s*\n?/g, '');136return out.replace(/\n{3,}/g, '\n\n');137}138139export function ensureSvelteLiveRootComponent(cwd, port) {140const file = path.join(cwd, SVELTE_LIVE_ROOT_COMPONENT);141fs.mkdirSync(path.dirname(file), { recursive: true });142fs.writeFileSync(file, buildSvelteLiveRootComponent(port), 'utf-8');143return file;144}145146export function buildSvelteLiveRootComponent(port) {147return `<script>148import { onMount } from 'svelte';149150const LIVE_URL = 'http://localhost:${Number(port)}/live.js';151const HOST_ID = 'impeccable-live-root';152153onMount(() => {154let host = document.querySelector('impeccable-live-root#' + HOST_ID) || document.getElementById(HOST_ID);155if (!host) {156host = document.createElement('impeccable-live-root');157host.id = HOST_ID;158document.body.appendChild(host);159}160161host.dataset.impeccableLiveAdapter = 'sveltekit';162host.style.setProperty('all', 'initial', 'important');163host.style.setProperty('display', 'block', 'important');164host.style.setProperty('position', 'fixed', 'important');165host.style.setProperty('top', '0', 'important');166host.style.setProperty('left', '0', 'important');167host.style.setProperty('width', '0', 'important');168host.style.setProperty('height', '0', 'important');169host.style.setProperty('overflow', 'visible', 'important');170host.style.setProperty('z-index', '2147483000', 'important');171host.style.setProperty('pointer-events', 'none', 'important');172173const root = host.shadowRoot || host.attachShadow({ mode: 'open' });174if (!root.querySelector('style[data-impeccable-live-reset]')) {175const reset = document.createElement('style');176reset.dataset.impeccableLiveReset = 'true';177reset.textContent = ':host, :host *, * { box-sizing: border-box; }';178root.appendChild(reset);179}180181window.__IMPECCABLE_LIVE_ADAPTER__ = 'sveltekit';182window.__IMPECCABLE_LIVE_UI_ROOT__ = root;183window.__IMPECCABLE_LIVE_CHROME_MOUNT__ = {184adapter: 'sveltekit',185version: 1,186host,187root,188};189190const script = document.createElement('script');191script.src = LIVE_URL;192script.async = true;193script.dataset.impeccableLiveScript = 'true';194document.head.appendChild(script);195196return () => {197script.remove();198if (window.__IMPECCABLE_LIVE_UI_ROOT__ === root) delete window.__IMPECCABLE_LIVE_UI_ROOT__;199if (window.__IMPECCABLE_LIVE_CHROME_MOUNT__?.root === root) delete window.__IMPECCABLE_LIVE_CHROME_MOUNT__;200if (window.__IMPECCABLE_LIVE_ADAPTER__ === 'sveltekit') delete window.__IMPECCABLE_LIVE_ADAPTER__;201};202});203</script>204`;205}206207function findSvelteKitAppHtml(cwd, config) {208const files = Array.isArray(config?.files) ? config.files : ['src/app.html'];209for (const rel of files) {210if (rel.includes('*')) continue;211const normalized = rel.split(path.sep).join('/');212if (!normalized.endsWith('app.html')) continue;213const abs = path.join(cwd, normalized);214if (fs.existsSync(abs)) return normalized;215}216const fallback = 'src/app.html';217return fs.existsSync(path.join(cwd, fallback)) ? fallback : null;218}219220function findSvelteKitLayout(cwd) {221const candidates = [222'src/routes/+layout.svelte',223'src/routes/(app)/+layout.svelte',224];225for (const rel of candidates) {226if (fs.existsSync(path.join(cwd, rel))) return rel;227}228return 'src/routes/+layout.svelte';229}230231function defaultSvelteLayout() {232return `<script>\n let { children } = $props();\n</script>\n\n{@render children?.()}\n`;233}234235function packageHasSvelteKit(cwd) {236const file = path.join(cwd, 'package.json');237if (!fs.existsSync(file)) return false;238try {239const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));240const deps = {241...(pkg.dependencies || {}),242...(pkg.devDependencies || {}),243...(pkg.peerDependencies || {}),244};245return Boolean(deps['@sveltejs/kit'] || deps['@sveltejs/vite-plugin-svelte'] || deps.svelte);246} catch {247return false;248}249}250251function fileIncludes(file, text) {252try {253return fs.readFileSync(file, 'utf-8').includes(text);254} catch {255return false;256}257}258259function pruneEmptyDir(dir, stopDir) {260let current = dir;261while (current.startsWith(stopDir) && current !== stopDir) {262try {263if (fs.readdirSync(current).length > 0) return;264fs.rmdirSync(current);265current = path.dirname(current);266} catch {267return;268}269}270}271272function escapeRegExp(value) {273return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');274}275