diff --git a/app/config/errors.php b/app/config/errors.php index 58e95cf5d4..c3b791f613 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -580,6 +580,11 @@ return [ 'description' => 'Build with the requested ID is already completed and cannot be canceled.', 'code' => 400, ], + Exception::BUILD_FAILED => [ + 'name' => Exception::BUILD_FAILED, + 'description' => 'Build with the requested ID failed. Please check the logs for more information.', + 'code' => 400, + ], /** Deployments */ Exception::DEPLOYMENT_NOT_FOUND => [ diff --git a/app/config/frameworks.php b/app/config/frameworks.php index 82fd70bf5b..afdf96145e 100644 --- a/app/config/frameworks.php +++ b/app/config/frameworks.php @@ -138,7 +138,7 @@ return [ 'vue' => [ 'key' => 'vue', 'name' => 'Vue.js', - 'screenshotSleep' => 3000, + 'screenshotSleep' => 5000, 'buildRuntime' => 'node-22', 'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'), 'adapters' => [ @@ -227,6 +227,23 @@ return [ ] ] ], + 'lynx' => [ + 'key' => 'lynx', + 'name' => 'Lynx', + 'screenshotSleep' => 5000, + 'buildRuntime' => 'node-22', + 'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'), + 'adapters' => [ + 'static' => [ + 'key' => 'static', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'outputDirectory' => './dist', + 'startCommand' => 'bash helpers/server.sh', + 'fallbackFile' => 'index.html' + ] + ] + ], 'flutter' => [ 'key' => 'flutter', 'name' => 'Flutter', diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index cc8a611416..bd2230a4a8 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -24718,6 +24718,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -25353,6 +25354,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -37040,6 +37042,11 @@ "description": "Site Template Name.", "x-example": "Starter site" }, + "tagline": { + "type": "string", + "description": "Short description of template", + "x-example": "Minimal web app integrating with Appwrite." + }, "demoUrl": { "type": "string", "description": "URL hosting a template demo.", @@ -37103,6 +37110,7 @@ "required": [ "key", "name", + "tagline", "demoUrl", "screenshotDark", "screenshotLight", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index ecda53b77b..706d84bd84 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -16816,6 +16816,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -17226,6 +17227,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 4d3ab5bce8..0ef66f4f9c 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -25221,6 +25221,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -25871,6 +25872,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -37623,6 +37625,11 @@ "description": "Site Template Name.", "x-example": "Starter site" }, + "tagline": { + "type": "string", + "description": "Short description of template", + "x-example": "Minimal web app integrating with Appwrite." + }, "demoUrl": { "type": "string", "description": "URL hosting a template demo.", @@ -37688,6 +37695,7 @@ "required": [ "key", "name", + "tagline", "demoUrl", "screenshotDark", "screenshotLight", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index c2d3b06ebc..59f7b5f7b8 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -17285,6 +17285,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" @@ -17714,6 +17715,7 @@ "sveltekit", "astro", "remix", + "lynx", "flutter", "vite", "other" diff --git a/app/config/templates/site.php b/app/config/templates/site.php index ba58940969..f5ffab0dad 100644 --- a/app/config/templates/site.php +++ b/app/config/templates/site.php @@ -87,15 +87,6 @@ const TEMPLATE_FRAMEWORKS = [ 'adapter' => 'static', 'fallbackFile' => '', ], - 'OTHER' => [ - 'key' => 'other', - 'name' => 'Other', - 'installCommand' => 'npm install', - 'buildCommand' => 'npm run build', - 'buildRuntime' => 'node-22', - 'adapter' => 'static', - 'fallbackFile' => 'index.html', - ], 'VITE' => [ 'key' => 'vite', 'name' => 'Vite', @@ -144,6 +135,16 @@ const TEMPLATE_FRAMEWORKS = [ 'adapter' => 'static', 'outputDirectory' => './', ], + 'LYNX' => [ + 'key' => 'lynx', + 'name' => 'Lynx', + 'installCommand' => 'npm install && cd web && npm install && cd ..', + 'buildCommand' => 'npm run build && cd web && npm run build && cd ..', + 'buildRuntime' => 'node-22', + 'adapter' => 'static', + 'outputDirectory' => './web/dist', + 'fallbackFile' => 'index.html', + ], ]; function getFramework(string $frameworkEnum, array $overrides) @@ -153,9 +154,30 @@ function getFramework(string $frameworkEnum, array $overrides) } return [ + [ + 'key' => 'lynx-starter', + 'name' => 'Lynx Starter', + 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.', + '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/lynx-starter-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/lynx-starter-light.png', + 'frameworks' => [ + getFramework('LYNX', [ + 'providerRootDirectory' => './lynx/starter', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates-for-sites', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.3.*', + 'variables' => [] + ], [ 'key' => 'vitepress', 'name' => 'Vitepress', + 'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::DOCUMENTATION], 'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png', 'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png', @@ -177,6 +199,8 @@ return [ [ 'key' => 'vuepress', 'name' => 'Vuepress', + 'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.', + 'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::DOCUMENTATION], 'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png', 'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png', @@ -198,6 +222,8 @@ return [ [ 'key' => 'docusaurus', 'name' => 'Docusaurus', + 'tagline' => 'Platform for documentation and knowledge sharing powered by React.', + 'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::DOCUMENTATION], 'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png', 'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png', @@ -219,6 +245,8 @@ return [ [ 'key' => 'nxt-lnk', 'name' => 'Nxt Lnk', + 'tagline' => 'Personal website for creators to merge all URLs to social profiles.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png', 'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png', @@ -236,6 +264,8 @@ return [ [ 'key' => 'magic-portfolio', 'name' => 'Magic Portfolio', + 'tagline' => 'Complex personal website to showcase your projects, articles, and more.', + 'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png', 'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png', @@ -253,6 +283,8 @@ return [ [ 'key' => 'littlelink', 'name' => 'LittleLink', + 'tagline' => 'Personal website for creators to merge all URLs to social profiles.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png', 'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png', @@ -270,6 +302,8 @@ return [ [ 'key' => 'logspot', 'name' => 'Logspot', + 'tagline' => 'Website to publish changelogs of your application.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::BLOG], 'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png', 'screenshotLight' => $url . '/images/sites/templates/logspot-light.png', @@ -290,6 +324,8 @@ return [ [ 'key' => 'astro-nano', 'name' => 'Astro Nano', + 'tagline' => 'Minimal personal website to showcase your projects, articles, and more.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png', 'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png', @@ -309,6 +345,8 @@ return [ [ 'key' => 'astro-starlight', 'name' => 'Astro Starlight', + 'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::DOCUMENTATION], 'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png', 'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png', @@ -328,6 +366,8 @@ return [ [ 'key' => 'astro-sphere', 'name' => 'Astro Sphere', + 'tagline' => 'Modern personal website to showcase your projects, articles, and more.', + 'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png', 'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png', @@ -347,6 +387,8 @@ return [ [ 'key' => 'astro-starlog', 'name' => 'Astro Starlog', + 'tagline' => 'Platform for publishing written content and media powered by Astro.', + 'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::BLOG], 'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png', 'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png', @@ -366,6 +408,8 @@ return [ [ 'key' => 'onelink', 'name' => 'Onelink', + 'tagline' => 'Personal website for creators to merge all URLs to social profiles.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png', 'screenshotLight' => $url . '/images/sites/templates/onelink-light.png', @@ -387,6 +431,8 @@ return [ 'key' => 'starter-for-flutter', 'name' => 'Flutter starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Flutter application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-flutter-light.png', 'frameworks' => [ @@ -430,6 +476,8 @@ return [ 'key' => 'starter-for-js', 'name' => 'JavaScript starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-js-light.png', 'frameworks' => [ @@ -472,6 +520,8 @@ return [ 'key' => 'starter-for-angular', 'name' => 'Angular starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Angular application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-angular-light.png', 'frameworks' => [ @@ -516,6 +566,8 @@ return [ 'key' => 'starter-for-svelte', 'name' => 'Svelte starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Svelte application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-svelte-light.png', 'frameworks' => [ @@ -558,6 +610,8 @@ return [ 'key' => 'starter-for-react', 'name' => 'React starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple React application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-react-light.png', 'frameworks' => [ @@ -600,6 +654,8 @@ return [ 'key' => 'starter-for-vue', 'name' => 'Vue starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Vue application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-vue-light.png', 'frameworks' => [ @@ -642,6 +698,8 @@ return [ 'key' => 'starter-for-react-native', 'name' => 'React Native starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple React Native application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-react-native-light.png', 'frameworks' => [ @@ -685,6 +743,8 @@ return [ 'key' => 'starter-for-nextjs', 'name' => 'Next.js starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Next.js 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-nextjs-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-nextjs-light.png', 'frameworks' => [ @@ -727,6 +787,8 @@ return [ 'key' => 'starter-for-nuxt', 'name' => 'Nuxt starter', 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png', 'screenshotLight' => $url . '/images/sites/templates/starter-for-nuxt-light.png', 'frameworks' => [ @@ -768,6 +830,8 @@ return [ [ 'key' => 'template-for-event', 'name' => 'Event template', + 'tagline' => 'Hackathon landing page with support for project submissions.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::EVENTS], 'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png', 'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png', @@ -804,6 +868,8 @@ return [ [ 'key' => 'template-for-portfolio', 'name' => 'Portfolio template', + 'tagline' => 'Simple personal website to showcase your projects, articles, and more.', + 'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::PORTFOLIO], 'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png', 'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png', @@ -821,6 +887,8 @@ return [ [ 'key' => 'template-for-store', 'name' => 'Store template', + 'tagline' => 'E-commerce platform for selling products with Stripe integration.', + 'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::ECOMMERCE], 'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png', 'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png', @@ -863,6 +931,8 @@ return [ [ 'key' => 'template-for-blog', 'name' => 'Blog template', + 'tagline' => 'Platform for publishing written content and media.', + 'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) 'useCases' => [UseCases::BLOG], 'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png', 'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png', @@ -880,6 +950,8 @@ return [ [ 'key' => 'astro-starter', 'name' => 'Astro starter', + 'tagline' => 'Sample application built with Astro, a content-driven web framework.', + '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/astro-starter-dark.png', 'screenshotLight' => $url . '/images/sites/templates/astro-starter-light.png', @@ -897,6 +969,8 @@ return [ [ 'key' => 'remix-starter', 'name' => 'Remix starter', + 'tagline' => 'Sample application built with Remix, a React meta-framework.', + '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/remix-starter-dark.png', 'screenshotLight' => $url . '/images/sites/templates/remix-starter-light.png', diff --git a/app/controllers/general.php b/app/controllers/general.php index 3f105484e1..5ce0a03471 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -269,8 +269,13 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported'); } - if ($deployment->getAttribute('status') !== 'ready') { - throw new AppwriteException(AppwriteException::BUILD_NOT_READY); + $allowAnyStatus = !\is_null($apiKey) && $apiKey->isDeploymentStatusIgnored(); + if (!$allowAnyStatus && $deployment->getAttribute('status') !== 'ready') { + if ($deployment->getAttribute('status') === 'failed') { + throw new AppwriteException(AppwriteException::BUILD_FAILED); + } else { + throw new AppwriteException(AppwriteException::BUILD_NOT_READY); + } } if ($type === 'function') { diff --git a/app/views/general/404.phtml b/app/views/general/404.phtml index 7ec1cfbf21..5e63344c8a 100644 --- a/app/views/general/404.phtml +++ b/app/views/general/404.phtml @@ -7,6 +7,8 @@