diff --git a/app/config/frameworks.php b/app/config/frameworks.php index f4d8ec7ffa..0ab4a8a7db 100644 --- a/app/config/frameworks.php +++ b/app/config/frameworks.php @@ -202,6 +202,31 @@ return [ ] ] ], + 'tanstack-start' => [ + 'key' => 'tanstack-start', + 'name' => 'TanStack Start', + 'screenshotSleep' => 3000, + 'buildRuntime' => 'node-22', + 'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'), + 'bundleCommand' => 'bash /usr/local/server/helpers/tanstack-start/bundle.sh', + 'envCommand' => 'source /usr/local/server/helpers/tanstack-start/env.sh', + 'adapters' => [ + 'ssr' => [ + 'key' => 'ssr', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'outputDirectory' => './dist', + 'startCommand' => 'bash helpers/tanstack-start/server.sh', + ], + 'static' => [ + 'key' => 'static', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'outputDirectory' => './dist/client', + 'startCommand' => 'bash helpers/server.sh', + ] + ] + ], 'remix' => [ 'key' => 'remix', 'name' => 'Remix', diff --git a/app/config/templates/site.php b/app/config/templates/site.php index c7e169f05e..e552a6b9ac 100644 --- a/app/config/templates/site.php +++ b/app/config/templates/site.php @@ -9,6 +9,11 @@ use Utopia\System\System; $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; $hostname = System::getEnv('_APP_DOMAIN', ''); +// Temporary fix until we can set _APP_DOMAIN to "localhost" instead of "traefik" +if (System::getEnv('_APP_ENV', 'development') === 'development') { + $hostname = 'localhost'; +} + $url = $protocol . '://' . $hostname; class UseCases @@ -111,6 +116,16 @@ const TEMPLATE_FRAMEWORKS = [ 'outputDirectory' => './dist', 'fallbackFile' => '+not-found.html', ], + 'TANSTACK_START' => [ + 'key' => 'tanstack-start', + 'name' => 'TanStack Start', + 'installCommand' => 'npm install', + 'buildCommand' => 'npm run build', + 'outputDirectory' => './dist', + 'buildRuntime' => 'node-22', + 'adapter' => 'ssr', + 'fallbackFile' => '', + ], 'ANGULAR' => [ 'key' => 'angular', 'name' => 'Angular', @@ -950,6 +965,50 @@ return [ ], ] ], + [ + 'key' => 'starter-for-tanstack-start', + 'name' => 'TanStack Start starter', + 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/starter-for-tanstack-start-light.png', + 'frameworks' => [ + getFramework('TANSTACK_START', [ + 'providerRootDirectory' => './', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'starter-for-tanstack-start', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.1.*', + 'variables' => [ + [ + 'name' => 'VITE_APPWRITE_ENDPOINT', + 'description' => 'Endpoint of Appwrite server', + 'value' => '{apiEndpoint}', + 'placeholder' => '{apiEndpoint}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_ID', + 'description' => 'Your Appwrite project ID', + 'value' => '{projectId}', + 'placeholder' => '{projectId}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_NAME', + 'description' => 'Your Appwrite project name', + 'value' => '{projectName}', + 'placeholder' => '{projectName}', + 'required' => true, + 'type' => 'text' + ], + ] + ], [ 'key' => 'starter-for-nuxt', 'name' => 'Nuxt starter', @@ -1327,6 +1386,25 @@ return [ 'providerVersion' => '0.3.*', 'variables' => [], ], + [ + 'key' => 'playground-for-tanstack-start', + 'name' => 'TanStack Start playground', + 'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.', + 'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'useCases' => [UseCases::STARTER], + 'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png', + 'frameworks' => [ + getFramework('TANSTACK_START', [ + 'providerRootDirectory' => './tanstack-start/starter', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates-for-sites', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.5.*', + 'variables' => [], + ], [ 'key' => 'playground-for-react-native', 'name' => 'React Native playground', diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index e8feff1bd4..673a05fa0f 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -29,12 +29,20 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Query\Cursor; +use Utopia\Detector\Detection\Framework\Analog; +use Utopia\Detector\Detection\Framework\Angular; use Utopia\Detector\Detection\Framework\Astro; use Utopia\Detector\Detection\Framework\Flutter; +use Utopia\Detector\Detection\Framework\Lynx; use Utopia\Detector\Detection\Framework\NextJs; use Utopia\Detector\Detection\Framework\Nuxt; +use Utopia\Detector\Detection\Framework\React; +use Utopia\Detector\Detection\Framework\ReactNative; use Utopia\Detector\Detection\Framework\Remix; +use Utopia\Detector\Detection\Framework\Svelte; use Utopia\Detector\Detection\Framework\SvelteKit; +use Utopia\Detector\Detection\Framework\TanStackStart; +use Utopia\Detector\Detection\Framework\Vue; use Utopia\Detector\Detection\Packager\NPM; use Utopia\Detector\Detection\Packager\PNPM; use Utopia\Detector\Detection\Packager\Yarn; @@ -58,6 +66,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; use Utopia\VCS\Adapter\Git\GitHub; +use Utopia\VCS\Exception\FileNotFound; use Utopia\VCS\Exception\RepositoryNotFound; use function Swoole\Coroutine\batch; @@ -818,7 +827,10 @@ App::post('/v1/vcs/github/installations/:installationId/detections') $files = \array_column($files, 'name'); $languages = $github->listRepositoryLanguages($owner, $repositoryName); - $detector = new Packager($files); + $detector = new Packager(); + foreach ($files as $file) { + $detector->addInput($file); + } $detector ->addOption(new Yarn()) ->addOption(new PNPM()) @@ -828,6 +840,14 @@ App::post('/v1/vcs/github/installations/:installationId/detections') $packager = !\is_null($detection) ? $detection->getName() : 'npm'; if ($type === 'framework') { + $packages = ''; + try { + $contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/package.json'); + $packages = $contentResponse['content'] ?? ''; + } catch (FileNotFound $e) { + // Continue detection without package.json + } + $output = new Document([ 'framework' => '', 'installCommand' => '', @@ -835,14 +855,27 @@ App::post('/v1/vcs/github/installations/:installationId/detections') 'outputDirectory' => '', ]); - $detector = new Framework($files, $packager); + $detector = new Framework($packager); + $detector->addInput($packages, Framework::INPUT_PACKAGES); + foreach ($files as $file) { + $detector->addInput($file, Framework::INPUT_FILE); + } + $detector - ->addOption(new Flutter()) - ->addOption(new Nuxt()) + ->addOption(new Analog()) + ->addOption(new Angular()) ->addOption(new Astro()) - ->addOption(new SvelteKit()) + ->addOption(new Flutter()) + ->addOption(new Lynx()) ->addOption(new NextJs()) - ->addOption(new Remix()); + ->addOption(new Nuxt()) + ->addOption(new React()) + ->addOption(new ReactNative()) + ->addOption(new Remix()) + ->addOption(new Svelte()) + ->addOption(new SvelteKit()) + ->addOption(new TanStackStart()) + ->addOption(new Vue()); $framework = $detector->detect(); @@ -877,7 +910,18 @@ App::post('/v1/vcs/github/installations/:installationId/detections') ]; foreach ($strategies as $strategy) { - $detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager); + $detector = new Runtime($strategy, $packager); + + if ($strategy === Strategy::LANGUAGES) { + foreach ($languages as $language) { + $detector->addInput($language); + } + } else { + foreach ($files as $file) { + $detector->addInput($file); + } + } + $detector ->addOption(new Node()) ->addOption(new Bun()) @@ -984,7 +1028,10 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $files = $github->listRepositoryContents($repo['organization'], $repo['name'], ''); $files = \array_column($files, 'name'); - $detector = new Packager($files); + $detector = new Packager(); + foreach ($files as $file) { + $detector->addInput($file); + } $detector ->addOption(new Yarn()) ->addOption(new PNPM()) @@ -994,14 +1041,35 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $packager = !\is_null($detection) ? $detection->getName() : 'npm'; if ($type === 'framework') { - $frameworkDetector = new Framework($files, $packager); + $packages = ''; + try { + $contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], 'package.json'); + $packages = $contentResponse['content'] ?? ''; + } catch (FileNotFound $e) { + // Continue detection without package.json + } + + $frameworkDetector = new Framework($packager); + $frameworkDetector->addInput($packages, Framework::INPUT_PACKAGES); + foreach ($files as $file) { + $frameworkDetector->addInput($file, Framework::INPUT_FILE); + } + $frameworkDetector - ->addOption(new Flutter()) - ->addOption(new Nuxt()) + ->addOption(new Analog()) + ->addOption(new Angular()) ->addOption(new Astro()) - ->addOption(new SvelteKit()) + ->addOption(new Flutter()) + ->addOption(new Lynx()) ->addOption(new NextJs()) - ->addOption(new Remix()); + ->addOption(new Nuxt()) + ->addOption(new React()) + ->addOption(new ReactNative()) + ->addOption(new Remix()) + ->addOption(new Svelte()) + ->addOption(new SvelteKit()) + ->addOption(new TanStackStart()) + ->addOption(new Vue()); $detectedFramework = $frameworkDetector->detect(); @@ -1026,7 +1094,16 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') ]; foreach ($strategies as $strategy) { - $detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager); + $detector = new Runtime($strategy, $packager); + if ($strategy === Strategy::LANGUAGES) { + foreach ($languages as $language) { + $detector->addInput($language); + } + } else { + foreach ($files as $file) { + $detector->addInput($file); + } + } $detector ->addOption(new Node()) ->addOption(new Bun()) diff --git a/composer.json b/composer.json index 80bb1dd39a..3e00015220 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", "utopia-php/database": "3.*", - "utopia-php/detector": "0.1.*", + "utopia-php/detector": "0.2.*", "utopia-php/domains": "0.9.*", "utopia-php/dns": "0.3.*", "utopia-php/dsn": "0.2.1", @@ -74,7 +74,7 @@ "utopia-php/swoole": "0.8.*", "utopia-php/system": "0.9.*", "utopia-php/telemetry": "0.1.*", - "utopia-php/vcs": "0.11.*", + "utopia-php/vcs": "0.12.*", "utopia-php/websocket": "0.3.*", "matomo/device-detector": "6.4.*", "dragonmantank/cron-expression": "3.4.*", diff --git a/composer.lock b/composer.lock index 99f9057d04..36c2c94265 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6f773bcdf583a863b0f2404958330f98", + "content-hash": "f826d3b283b10af98dfd565c4187a83a", "packages": [ { "name": "adhocore/jwt", @@ -3898,16 +3898,16 @@ }, { "name": "utopia-php/detector", - "version": "0.1.5", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/detector.git", - "reference": "b5d6ba51352485b524589bc0ee8d07a9efafe718" + "reference": "795ed56169af833fd6a4ea58a6c747e05ccc7ba6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/detector/zipball/b5d6ba51352485b524589bc0ee8d07a9efafe718", - "reference": "b5d6ba51352485b524589bc0ee8d07a9efafe718", + "url": "https://api.github.com/repos/utopia-php/detector/zipball/795ed56169af833fd6a4ea58a6c747e05ccc7ba6", + "reference": "795ed56169af833fd6a4ea58a6c747e05ccc7ba6", "shasum": "" }, "require": { @@ -3937,9 +3937,9 @@ ], "support": { "issues": "https://github.com/utopia-php/detector/issues", - "source": "https://github.com/utopia-php/detector/tree/0.1.5" + "source": "https://github.com/utopia-php/detector/tree/0.2.0" }, - "time": "2025-05-19T11:01:28+00:00" + "time": "2025-10-21T13:57:30+00:00" }, { "name": "utopia-php/dns", @@ -5095,16 +5095,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.11.0", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "0e665eaa7d906168525bf6aac50b6bcc3e4fe528" + "reference": "28457cf347972c4ec95d3ca77776a4921364a665" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/0e665eaa7d906168525bf6aac50b6bcc3e4fe528", - "reference": "0e665eaa7d906168525bf6aac50b6bcc3e4fe528", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/28457cf347972c4ec95d3ca77776a4921364a665", + "reference": "28457cf347972c4ec95d3ca77776a4921364a665", "shasum": "" }, "require": { @@ -5138,9 +5138,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.11.0" + "source": "https://github.com/utopia-php/vcs/tree/0.12.0" }, - "time": "2025-07-23T13:54:58+00:00" + "time": "2025-10-22T12:58:29+00:00" }, { "name": "utopia-php/websocket", @@ -5318,16 +5318,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.4.7", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "a61c8be551e10f4970bf46f75a54e4b0385c550d" + "reference": "5970defc3c6e64817fe9847c0b33c87af71709c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a61c8be551e10f4970bf46f75a54e4b0385c550d", - "reference": "a61c8be551e10f4970bf46f75a54e4b0385c550d", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/5970defc3c6e64817fe9847c0b33c87af71709c5", + "reference": "5970defc3c6e64817fe9847c0b33c87af71709c5", "shasum": "" }, "require": { @@ -5363,9 +5363,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.4.7" + "source": "https://github.com/appwrite/sdk-generator/tree/1.4.11" }, - "time": "2025-10-22T06:03:44+00:00" + "time": "2025-10-24T10:03:09+00:00" }, { "name": "doctrine/annotations", @@ -8832,7 +8832,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8856,5 +8856,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml index b72f12a116..c340733cc2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -966,7 +966,7 @@ services: hostname: exc1 <<: *x-logging stop_signal: SIGINT - image: openruntimes/executor:0.11.0 + image: openruntimes/executor:0.11.4 restart: unless-stopped networks: - appwrite diff --git a/public/images/sites/templates/playground-for-tanstack-start-dark.png b/public/images/sites/templates/playground-for-tanstack-start-dark.png new file mode 100644 index 0000000000..2e93bba5e9 Binary files /dev/null and b/public/images/sites/templates/playground-for-tanstack-start-dark.png differ diff --git a/public/images/sites/templates/playground-for-tanstack-start-light.png b/public/images/sites/templates/playground-for-tanstack-start-light.png new file mode 100644 index 0000000000..2e93bba5e9 Binary files /dev/null and b/public/images/sites/templates/playground-for-tanstack-start-light.png differ diff --git a/public/images/sites/templates/starter-for-tanstack-start-dark.png b/public/images/sites/templates/starter-for-tanstack-start-dark.png new file mode 100644 index 0000000000..ccd90505e6 Binary files /dev/null and b/public/images/sites/templates/starter-for-tanstack-start-dark.png differ diff --git a/public/images/sites/templates/starter-for-tanstack-start-light.png b/public/images/sites/templates/starter-for-tanstack-start-light.png new file mode 100644 index 0000000000..ccd90505e6 Binary files /dev/null and b/public/images/sites/templates/starter-for-tanstack-start-light.png differ diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 95a9a9fae7..50bda900fd 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -873,7 +873,10 @@ class Builds extends Action $files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces $files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./ - $detector = new Rendering($files, $resource->getAttribute('framework', '')); + $detector = new Rendering($resource->getAttribute('framework', '')); + foreach ($files as $file) { + $detector->addInput($file); + } $detector ->addOption(new SSR()) ->addOption(new XStatic()); diff --git a/src/Appwrite/Platform/Tasks/Screenshot.php b/src/Appwrite/Platform/Tasks/Screenshot.php index 5a4d8a76b7..94609de01e 100644 --- a/src/Appwrite/Platform/Tasks/Screenshot.php +++ b/src/Appwrite/Platform/Tasks/Screenshot.php @@ -2,6 +2,9 @@ namespace Appwrite\Platform\Tasks; +// Example usage: docker compose exec appwrite screenshot --templateId="playground-for-tanstack-start" +// Expected output: public/images/sites/templates/playground-for-tanstack-start-light.png (and dark.png) + use Appwrite\ID; use Tests\E2E\Client; use Utopia\CLI\Console; diff --git a/tests/e2e/Services/Sites/SitesCustomClientTest.php b/tests/e2e/Services/Sites/SitesCustomClientTest.php index 42fe26d216..0434e4338b 100644 --- a/tests/e2e/Services/Sites/SitesCustomClientTest.php +++ b/tests/e2e/Services/Sites/SitesCustomClientTest.php @@ -131,8 +131,8 @@ class SitesCustomClientTest extends Scope $this->assertEquals('github', $template['body']['vcsProvider']); $this->assertEquals('Simple React application integrated with Appwrite SDK.', $template['body']['tagline']); $this->assertIsArray($template['body']['frameworks']); - $this->assertEquals('http://'. $hostname . '/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']); - $this->assertEquals('http://' . $hostname . '/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']); + $this->assertStringContainsString('/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']); + $this->assertStringContainsString('/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']); /** * Test for FAILURE