From faf42b674d41ca4e03bdc5d71f690ebc999499a7 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:10:25 +0530 Subject: [PATCH 1/8] feat: add adapter detection via config file content --- src/Detection/Framework.php | 17 +++++++++++++++++ src/Detection/Framework/Astro.php | 18 ++++++++++++++++++ src/Detection/Framework/Remix.php | 14 ++++++++++++++ src/Detection/Framework/SvelteKit.php | 11 +++++++++++ src/Detection/Framework/TanStackStart.php | 10 ++++++++++ 5 files changed, 70 insertions(+) diff --git a/src/Detection/Framework.php b/src/Detection/Framework.php index fceaffb..5058e4d 100644 --- a/src/Detection/Framework.php +++ b/src/Detection/Framework.php @@ -39,4 +39,21 @@ abstract public function getInstallCommand(): string; abstract public function getBuildCommand(): string; abstract public function getOutputDirectory(): string; + + /** + * @return array Config files to read for adapter detection, in priority order. + */ + public function getConfigFiles(): array + { + return []; + } + + /** + * Detect the adapter ('ssr' or 'static') from config file content. + * Returns empty string if detection is not possible. + */ + public function getAdapter(string $configContent): string + { + return ''; + } } diff --git a/src/Detection/Framework/Astro.php b/src/Detection/Framework/Astro.php index 6db7fe0..1143aaa 100644 --- a/src/Detection/Framework/Astro.php +++ b/src/Detection/Framework/Astro.php @@ -67,4 +67,22 @@ public function getOutputDirectory(): string { return './dist'; } + + public function getConfigFiles(): array + { + return ['astro.config.mjs', 'astro.config.js', 'astro.config.ts']; + } + + public function getAdapter(string $configContent): string + { + // 'server' and 'hybrid' both require the SSR adapter + if (\str_contains($configContent, "output: 'server'") || \str_contains($configContent, 'output: "server"')) { + return 'ssr'; + } + if (\str_contains($configContent, "output: 'hybrid'") || \str_contains($configContent, 'output: "hybrid"')) { + return 'ssr'; + } + + return 'static'; + } } diff --git a/src/Detection/Framework/Remix.php b/src/Detection/Framework/Remix.php index 6b68c83..8cc6bde 100644 --- a/src/Detection/Framework/Remix.php +++ b/src/Detection/Framework/Remix.php @@ -49,4 +49,18 @@ public function getOutputDirectory(): string { return './build'; } + + public function getAdapter(string $configContent): string + { + // @remix-run/serve in dependencies indicates SSR; static adapters use build/client output only + if (\str_contains($configContent, '"@remix-run/serve"') || \str_contains($configContent, "'@remix-run/serve'")) { + return 'ssr'; + } + // Vite-based Remix v2+ with SSR adapters (e.g. @remix-run/express, @remix-run/node) + if (\str_contains($configContent, '@remix-run/express') || \str_contains($configContent, '@remix-run/node')) { + return 'ssr'; + } + + return 'ssr'; // Remix defaults to SSR; static mode requires explicit adapter configuration + } } diff --git a/src/Detection/Framework/SvelteKit.php b/src/Detection/Framework/SvelteKit.php index 0c35be1..0d77eb0 100644 --- a/src/Detection/Framework/SvelteKit.php +++ b/src/Detection/Framework/SvelteKit.php @@ -49,4 +49,15 @@ public function getOutputDirectory(): string { return './build'; } + + public function getConfigFiles(): array + { + return ['svelte.config.js', 'svelte.config.ts']; + } + + public function getAdapter(string $configContent): string + { + // Detect static adapter from package.json dependencies or svelte.config content + return \str_contains($configContent, '@sveltejs/adapter-static') ? 'static' : 'ssr'; + } } diff --git a/src/Detection/Framework/TanStackStart.php b/src/Detection/Framework/TanStackStart.php index 921c82d..87c8c88 100644 --- a/src/Detection/Framework/TanStackStart.php +++ b/src/Detection/Framework/TanStackStart.php @@ -49,4 +49,14 @@ public function getOutputDirectory(): string { return './.output'; } + + public function getConfigFiles(): array + { + return ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']; + } + + public function getAdapter(string $configContent): string + { + return \str_contains($configContent, 'prerender') ? 'static' : 'ssr'; + } } From 2a800b4b86a82b98fbf77837a5516f87ee190aeb Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:13:13 +0530 Subject: [PATCH 2/8] test: add adapter detection unit tests for TanStack, SvelteKit, Astro, Remix --- tests/unit/DetectorTest.php | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unit/DetectorTest.php b/tests/unit/DetectorTest.php index f180a00..1a1cf37 100644 --- a/tests/unit/DetectorTest.php +++ b/tests/unit/DetectorTest.php @@ -798,4 +798,43 @@ public function testFrameworkEdgeCases(string $assertion, array $files, string $ $this->assertSame($framework, $detection->getName(), $assertion); } + + public function testTanStackStartAdapterDetection(): void + { + $fw = new TanStackStart(); + + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); + $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); + $this->assertNotEmpty($fw->getConfigFiles()); + } + + public function testSvelteKitAdapterDetection(): void + { + $fw = new SvelteKit(); + + $this->assertSame('ssr', $fw->getAdapter('import adapter from \'@sveltejs/adapter-auto\'; export default { kit: { adapter: adapter() } }')); + $this->assertSame('static', $fw->getAdapter('import adapter from \'@sveltejs/adapter-static\'; export default { kit: { adapter: adapter() } }')); + $this->assertSame('static', $fw->getAdapter('{"dependencies":{"@sveltejs/adapter-static":"^3.0.0"}}')); + $this->assertNotEmpty($fw->getConfigFiles()); + } + + public function testAstroAdapterDetection(): void + { + $fw = new Astro(); + + $this->assertSame('static', $fw->getAdapter('export default defineConfig({ integrations: [] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: \'server\', adapter: node({ mode: \'standalone\' }) })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: "server" })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: \'hybrid\' })')); + $this->assertNotEmpty($fw->getConfigFiles()); + } + + public function testRemixAdapterDetection(): void + { + $fw = new Remix(); + + $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/react":"^2.0.0"}}')); + $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/serve":"^2.0.0"}}')); + $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/node":"^2.0.0"}}')); + } } From f21c0eeb20cdf4af8c79e34dbdac662727e921d3 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:15:41 +0530 Subject: [PATCH 3/8] fix: add docblocks, strip redundant comments, simplify Remix adapter --- src/Detection/Framework/Astro.php | 4 +++- src/Detection/Framework/Remix.php | 11 +---------- src/Detection/Framework/SvelteKit.php | 4 +++- src/Detection/Framework/TanStackStart.php | 3 +++ 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Detection/Framework/Astro.php b/src/Detection/Framework/Astro.php index 1143aaa..e3e07ed 100644 --- a/src/Detection/Framework/Astro.php +++ b/src/Detection/Framework/Astro.php @@ -68,6 +68,9 @@ public function getOutputDirectory(): string return './dist'; } + /** + * @return array + */ public function getConfigFiles(): array { return ['astro.config.mjs', 'astro.config.js', 'astro.config.ts']; @@ -75,7 +78,6 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - // 'server' and 'hybrid' both require the SSR adapter if (\str_contains($configContent, "output: 'server'") || \str_contains($configContent, 'output: "server"')) { return 'ssr'; } diff --git a/src/Detection/Framework/Remix.php b/src/Detection/Framework/Remix.php index 8cc6bde..40cbf76 100644 --- a/src/Detection/Framework/Remix.php +++ b/src/Detection/Framework/Remix.php @@ -52,15 +52,6 @@ public function getOutputDirectory(): string public function getAdapter(string $configContent): string { - // @remix-run/serve in dependencies indicates SSR; static adapters use build/client output only - if (\str_contains($configContent, '"@remix-run/serve"') || \str_contains($configContent, "'@remix-run/serve'")) { - return 'ssr'; - } - // Vite-based Remix v2+ with SSR adapters (e.g. @remix-run/express, @remix-run/node) - if (\str_contains($configContent, '@remix-run/express') || \str_contains($configContent, '@remix-run/node')) { - return 'ssr'; - } - - return 'ssr'; // Remix defaults to SSR; static mode requires explicit adapter configuration + return 'ssr'; } } diff --git a/src/Detection/Framework/SvelteKit.php b/src/Detection/Framework/SvelteKit.php index 0d77eb0..226c3e9 100644 --- a/src/Detection/Framework/SvelteKit.php +++ b/src/Detection/Framework/SvelteKit.php @@ -50,6 +50,9 @@ public function getOutputDirectory(): string return './build'; } + /** + * @return array + */ public function getConfigFiles(): array { return ['svelte.config.js', 'svelte.config.ts']; @@ -57,7 +60,6 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - // Detect static adapter from package.json dependencies or svelte.config content return \str_contains($configContent, '@sveltejs/adapter-static') ? 'static' : 'ssr'; } } diff --git a/src/Detection/Framework/TanStackStart.php b/src/Detection/Framework/TanStackStart.php index 87c8c88..1c317e2 100644 --- a/src/Detection/Framework/TanStackStart.php +++ b/src/Detection/Framework/TanStackStart.php @@ -50,6 +50,9 @@ public function getOutputDirectory(): string return './.output'; } + /** + * @return array + */ public function getConfigFiles(): array { return ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']; From 7a5b650199275c2009de3e2bc810a52d1c6f78a6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:25:10 +0530 Subject: [PATCH 4/8] fix: use regex for robust adapter detection, handle edge cases --- src/Detection/Framework/Astro.php | 7 +++---- src/Detection/Framework/SvelteKit.php | 4 +++- src/Detection/Framework/TanStackStart.php | 8 +++++++- tests/unit/DetectorTest.php | 6 ++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Detection/Framework/Astro.php b/src/Detection/Framework/Astro.php index e3e07ed..3f6762f 100644 --- a/src/Detection/Framework/Astro.php +++ b/src/Detection/Framework/Astro.php @@ -78,10 +78,9 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - if (\str_contains($configContent, "output: 'server'") || \str_contains($configContent, 'output: "server"')) { - return 'ssr'; - } - if (\str_contains($configContent, "output: 'hybrid'") || \str_contains($configContent, 'output: "hybrid"')) { + $stripped = \preg_replace('/\/\/[^\n]*/', '', $configContent) ?? $configContent; + + if (\preg_match('/\boutput\s*:\s*[\x27\x22](?:server|hybrid)[\x27\x22]/', $stripped)) { return 'ssr'; } diff --git a/src/Detection/Framework/SvelteKit.php b/src/Detection/Framework/SvelteKit.php index 226c3e9..3a612b8 100644 --- a/src/Detection/Framework/SvelteKit.php +++ b/src/Detection/Framework/SvelteKit.php @@ -60,6 +60,8 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - return \str_contains($configContent, '@sveltejs/adapter-static') ? 'static' : 'ssr'; + $stripped = \preg_replace('/\/\/[^\n]*/', '', $configContent) ?? $configContent; + + return \str_contains($stripped, '@sveltejs/adapter-static') ? 'static' : 'ssr'; } } diff --git a/src/Detection/Framework/TanStackStart.php b/src/Detection/Framework/TanStackStart.php index 1c317e2..8ca58da 100644 --- a/src/Detection/Framework/TanStackStart.php +++ b/src/Detection/Framework/TanStackStart.php @@ -60,6 +60,12 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - return \str_contains($configContent, 'prerender') ? 'static' : 'ssr'; + $stripped = \preg_replace('/\/\/[^\n]*/', '', $configContent) ?? $configContent; + + if (!\preg_match('/\bprerender\b/', $stripped) || \preg_match('/\bprerender\s*:\s*false\b/', $stripped)) { + return 'ssr'; + } + + return 'static'; } } diff --git a/tests/unit/DetectorTest.php b/tests/unit/DetectorTest.php index 1a1cf37..437a2b4 100644 --- a/tests/unit/DetectorTest.php +++ b/tests/unit/DetectorTest.php @@ -805,6 +805,8 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); + $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -815,6 +817,7 @@ public function testSvelteKitAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import adapter from \'@sveltejs/adapter-auto\'; export default { kit: { adapter: adapter() } }')); $this->assertSame('static', $fw->getAdapter('import adapter from \'@sveltejs/adapter-static\'; export default { kit: { adapter: adapter() } }')); $this->assertSame('static', $fw->getAdapter('{"dependencies":{"@sveltejs/adapter-static":"^3.0.0"}}')); + $this->assertSame('ssr', $fw->getAdapter('// import adapter from \'@sveltejs/adapter-static\'' . "\n" . 'import adapter from \'@sveltejs/adapter-auto\'')); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -826,6 +829,8 @@ public function testAstroAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: \'server\', adapter: node({ mode: \'standalone\' }) })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: "server" })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: \'hybrid\' })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output : \'server\' })')); + $this->assertSame('static', $fw->getAdapter('// output: \'server\'' . "\n" . 'export default defineConfig({})')); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -836,5 +841,6 @@ public function testRemixAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/react":"^2.0.0"}}')); $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/serve":"^2.0.0"}}')); $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/node":"^2.0.0"}}')); + $this->assertSame('ssr', $fw->getAdapter('')); } } From 239c062f50bf135fe3a57ceee228f57bd22dd4f6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:33:34 +0530 Subject: [PATCH 5/8] fix: skip URLs in comment stripping, expose package.json in getConfigFiles --- src/Detection/Framework/Astro.php | 2 +- src/Detection/Framework/Remix.php | 8 ++++++++ src/Detection/Framework/SvelteKit.php | 4 ++-- src/Detection/Framework/TanStackStart.php | 2 +- tests/unit/DetectorTest.php | 4 ++++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Detection/Framework/Astro.php b/src/Detection/Framework/Astro.php index 3f6762f..3b1e46d 100644 --- a/src/Detection/Framework/Astro.php +++ b/src/Detection/Framework/Astro.php @@ -78,7 +78,7 @@ public function getConfigFiles(): array public function getAdapter(string $configContent): string { - $stripped = \preg_replace('/\/\/[^\n]*/', '', $configContent) ?? $configContent; + $stripped = \preg_replace('/(? + */ + public function getConfigFiles(): array + { + return ['package.json']; + } + public function getAdapter(string $configContent): string { return 'ssr'; diff --git a/src/Detection/Framework/SvelteKit.php b/src/Detection/Framework/SvelteKit.php index 3a612b8..676a31c 100644 --- a/src/Detection/Framework/SvelteKit.php +++ b/src/Detection/Framework/SvelteKit.php @@ -55,12 +55,12 @@ public function getOutputDirectory(): string */ public function getConfigFiles(): array { - return ['svelte.config.js', 'svelte.config.ts']; + return ['svelte.config.js', 'svelte.config.ts', 'package.json']; } public function getAdapter(string $configContent): string { - $stripped = \preg_replace('/\/\/[^\n]*/', '', $configContent) ?? $configContent; + $stripped = \preg_replace('/(?assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); + $this->assertSame('static', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -818,6 +819,7 @@ public function testSvelteKitAdapterDetection(): void $this->assertSame('static', $fw->getAdapter('import adapter from \'@sveltejs/adapter-static\'; export default { kit: { adapter: adapter() } }')); $this->assertSame('static', $fw->getAdapter('{"dependencies":{"@sveltejs/adapter-static":"^3.0.0"}}')); $this->assertSame('ssr', $fw->getAdapter('// import adapter from \'@sveltejs/adapter-static\'' . "\n" . 'import adapter from \'@sveltejs/adapter-auto\'')); + $this->assertContains('package.json', $fw->getConfigFiles()); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -831,6 +833,7 @@ public function testAstroAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: \'hybrid\' })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output : \'server\' })')); $this->assertSame('static', $fw->getAdapter('// output: \'server\'' . "\n" . 'export default defineConfig({})')); + $this->assertSame('ssr', $fw->getAdapter('site: "https://example.com",' . "\n" . 'output: "server"')); $this->assertNotEmpty($fw->getConfigFiles()); } @@ -842,5 +845,6 @@ public function testRemixAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/serve":"^2.0.0"}}')); $this->assertSame('ssr', $fw->getAdapter('{"dependencies":{"@remix-run/node":"^2.0.0"}}')); $this->assertSame('ssr', $fw->getAdapter('')); + $this->assertContains('package.json', $fw->getConfigFiles()); } } From 6e7731eb15452bcdc19ff683d682810070ba46e0 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:41:30 +0530 Subject: [PATCH 6/8] fix: add svelte.config.mjs to SvelteKit getConfigFiles --- src/Detection/Framework/SvelteKit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Detection/Framework/SvelteKit.php b/src/Detection/Framework/SvelteKit.php index 676a31c..331a295 100644 --- a/src/Detection/Framework/SvelteKit.php +++ b/src/Detection/Framework/SvelteKit.php @@ -55,7 +55,7 @@ public function getOutputDirectory(): string */ public function getConfigFiles(): array { - return ['svelte.config.js', 'svelte.config.ts', 'package.json']; + return ['svelte.config.js', 'svelte.config.mjs', 'svelte.config.ts', 'package.json']; } public function getAdapter(string $configContent): string From c9c0cad93e7f331265794a367badee57f5991825 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:50:01 +0530 Subject: [PATCH 7/8] fix: add template literal support to Astro output detection --- src/Detection/Framework/Astro.php | 2 +- tests/unit/DetectorTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Detection/Framework/Astro.php b/src/Detection/Framework/Astro.php index 3b1e46d..e16b684 100644 --- a/src/Detection/Framework/Astro.php +++ b/src/Detection/Framework/Astro.php @@ -80,7 +80,7 @@ public function getAdapter(string $configContent): string { $stripped = \preg_replace('/(?assertSame('ssr', $fw->getAdapter('export default defineConfig({ output : \'server\' })')); $this->assertSame('static', $fw->getAdapter('// output: \'server\'' . "\n" . 'export default defineConfig({})')); $this->assertSame('ssr', $fw->getAdapter('site: "https://example.com",' . "\n" . 'output: "server"')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: `server` })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ output: `hybrid` })')); $this->assertNotEmpty($fw->getConfigFiles()); } From c63a25d3c2fb0a461e16b1824eb1571df577b910 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 17 Jun 2026 12:56:05 +0530 Subject: [PATCH 8/8] fix: handle quoted prerender key in TanStack adapter detection --- src/Detection/Framework/TanStackStart.php | 2 +- tests/unit/DetectorTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Detection/Framework/TanStackStart.php b/src/Detection/Framework/TanStackStart.php index 721402e..74c1af0 100644 --- a/src/Detection/Framework/TanStackStart.php +++ b/src/Detection/Framework/TanStackStart.php @@ -62,7 +62,7 @@ public function getAdapter(string $configContent): string { $stripped = \preg_replace('/(?assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); $this->assertSame('static', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); $this->assertNotEmpty($fw->getConfigFiles());