2025-02-27 14:58:33 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Appwrite\Platform\Tasks;
|
|
|
|
|
|
2025-10-22 11:40:49 +00:00
|
|
|
// Example usage: docker compose exec appwrite screenshot --templateId="playground-for-tanstack-start"
|
2025-12-11 09:31:05 +00:00
|
|
|
// Example of env vars flag: --variables="{\"VITE_FORMSPREE_FORM_ID\":\"xvgkbzll\", \"VITE_FORMSPREE_FORM_SECRET\":\"some_secret\"}"
|
2025-10-22 11:40:49 +00:00
|
|
|
// Expected output: public/images/sites/templates/playground-for-tanstack-start-light.png (and dark.png)
|
|
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
use Appwrite\ID;
|
|
|
|
|
use Tests\E2E\Client;
|
|
|
|
|
use Utopia\CLI\Console;
|
|
|
|
|
use Utopia\Config\Config;
|
|
|
|
|
use Utopia\Platform\Action;
|
2025-12-11 09:31:05 +00:00
|
|
|
use Utopia\System\System;
|
2025-02-27 14:58:33 +00:00
|
|
|
use Utopia\Validator\Text;
|
|
|
|
|
|
|
|
|
|
class Screenshot extends Action
|
|
|
|
|
{
|
|
|
|
|
public static function getName(): string
|
|
|
|
|
{
|
|
|
|
|
return 'screenshot';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
$this
|
|
|
|
|
->desc('Create Site template screenshot')
|
|
|
|
|
->param('templateId', '', new Text(128), 'Template ID.')
|
2025-12-11 09:31:05 +00:00
|
|
|
->param('variables', '', new Text(16384), 'JSON of env variables to use when setting up the site.')
|
2025-06-04 08:37:43 +00:00
|
|
|
->callback($this->action(...));
|
2025-02-27 14:58:33 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-11 09:31:05 +00:00
|
|
|
public function action(string $templateId, string $variables): void
|
2025-02-27 14:58:33 +00:00
|
|
|
{
|
2025-12-11 09:31:05 +00:00
|
|
|
$variables = \json_decode($variables, true);
|
|
|
|
|
if(!\is_array($variables)) {
|
|
|
|
|
throw new \Exception('Invalid JSON in --variables flag');
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-11 08:50:02 +00:00
|
|
|
$templates = Config::getParam('templates-site', []);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$allowedTemplates = \array_filter($templates, function ($item) use ($templateId) {
|
|
|
|
|
return $item['key'] === $templateId;
|
|
|
|
|
});
|
|
|
|
|
$template = \array_shift($allowedTemplates);
|
|
|
|
|
|
|
|
|
|
if (empty($template)) {
|
2025-03-08 19:44:54 +00:00
|
|
|
throw new \Exception("Template {$templateId} not found. Find correct ID in app/config/templates/site.php");
|
2025-02-27 14:58:33 +00:00
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Found: " . $template['name']);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$client = new Client();
|
|
|
|
|
$client->setEndpoint('http://localhost/v1');
|
|
|
|
|
$client->addHeader('origin', 'http://localhost');
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Register
|
|
|
|
|
$email = uniqid() . 'user@localhost.test';
|
|
|
|
|
$password = 'password';
|
2025-03-11 13:16:27 +00:00
|
|
|
|
|
|
|
|
Console::info("Email: {$email}");
|
|
|
|
|
Console::info("Pass: {$password}");
|
|
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$user = $client->call(Client::METHOD_POST, '/account', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
], [
|
|
|
|
|
'userId' => ID::unique(),
|
|
|
|
|
'email' => $email,
|
|
|
|
|
'password' => $password,
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($user['headers']['status-code'] !== 201) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($user));
|
|
|
|
|
throw new \Exception("Failed to register user");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("User created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Login
|
|
|
|
|
$session = $client->call(Client::METHOD_POST, '/account/sessions/email', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
], [
|
|
|
|
|
'email' => $email,
|
|
|
|
|
'password' => $password,
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($session['headers']['status-code'] !== 201) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($session));
|
|
|
|
|
throw new \Exception("Failed to login user");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Session created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$secret = $session['cookies']['a_session_console'];
|
2025-02-27 16:46:47 +00:00
|
|
|
$cookieConsole = 'a_session_console=' . $secret;
|
|
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Create organization
|
|
|
|
|
$team = $client->call(Client::METHOD_POST, '/teams', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
|
|
|
|
'teamId' => ID::unique(),
|
|
|
|
|
'name' => 'Demo Project Team',
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($team['headers']['status-code'] !== 201) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($team));
|
|
|
|
|
throw new \Exception("Failed to create team");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Team created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 16:28:25 +00:00
|
|
|
$projectName = 'Demo Project';
|
|
|
|
|
$projectId = ID::unique();
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Create project
|
|
|
|
|
$project = $client->call(Client::METHOD_POST, '/projects', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
2025-02-27 16:28:25 +00:00
|
|
|
'projectId' => $projectId,
|
2025-02-27 14:58:33 +00:00
|
|
|
'region' => 'default',
|
2025-02-27 16:28:25 +00:00
|
|
|
'name' => $projectName,
|
2025-02-27 14:58:33 +00:00
|
|
|
'teamId' => $team['body']['$id'],
|
|
|
|
|
'description' => 'Demo Project Description',
|
|
|
|
|
'url' => 'https://appwrite.io',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-02-27 16:46:47 +00:00
|
|
|
if ($project['headers']['status-code'] !== 201) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($project));
|
|
|
|
|
throw new \Exception("Failed to create project");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Project created");
|
|
|
|
|
|
|
|
|
|
$projectId = $project['body']['$id'];
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$framework = $template['frameworks'][0];
|
2025-12-11 09:31:05 +00:00
|
|
|
|
|
|
|
|
// Use best specifications to prevent out-of-memory during build
|
|
|
|
|
$specifications = Config::getParam('specifications', []);
|
|
|
|
|
$specifications = array_keys($specifications);
|
|
|
|
|
$specification = \end($specifications);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Create site
|
|
|
|
|
$site = $client->call(Client::METHOD_POST, '/sites', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => $projectId,
|
|
|
|
|
'x-appwrite-mode' => 'admin',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
|
|
|
|
'siteId' => ID::unique(),
|
2025-12-11 09:31:05 +00:00
|
|
|
'specification' => $specification,
|
2025-02-27 14:58:33 +00:00
|
|
|
'name' => $template["name"],
|
|
|
|
|
'framework' => $framework['key'],
|
|
|
|
|
'adapter' => $framework['adapter'],
|
2025-03-11 10:49:31 +00:00
|
|
|
'buildCommand' => $framework['buildCommand'] ?? '',
|
2025-02-27 14:58:33 +00:00
|
|
|
'buildRuntime' => $framework['buildRuntime'],
|
2025-03-11 10:49:31 +00:00
|
|
|
'fallbackFile' => $framework['fallbackFile'] ?? '',
|
|
|
|
|
'installCommand' => $framework['installCommand'] ?? '',
|
|
|
|
|
'outputDirectory' => $framework['outputDirectory'] ?? '',
|
2025-02-27 14:58:33 +00:00
|
|
|
'providerRootDirectory' => $framework['providerRootDirectory'],
|
2025-04-10 14:11:38 +00:00
|
|
|
'timeout' => 30
|
2025-02-27 14:58:33 +00:00
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($site['headers']['status-code'] !== 201) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($site));
|
|
|
|
|
throw new \Exception("Failed to create site");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Site created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$siteId = $site['body']['$id'];
|
2025-12-11 09:31:05 +00:00
|
|
|
|
|
|
|
|
// Prepare API key, incase it's needed as variable
|
|
|
|
|
$response = $client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
|
|
|
|
'name' => 'Screenshot API key',
|
|
|
|
|
'scopes' => \array_keys(Config::getParam('scopes', []))
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($response['headers']['status-code'] !== 201) {
|
|
|
|
|
Console::error(\json_encode($response));
|
|
|
|
|
throw new \Exception("Failed to create API key");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$apiKey = $response['body']['secret'];
|
|
|
|
|
|
|
|
|
|
Console::info("API key created");
|
|
|
|
|
|
|
|
|
|
$variables['APPWRITE_API_KEY'] = $apiKey;
|
2025-02-27 16:28:25 +00:00
|
|
|
|
|
|
|
|
// Create variables
|
2025-02-27 16:46:47 +00:00
|
|
|
if (!empty($template['variables'] ?? [])) {
|
|
|
|
|
foreach ($template['variables'] as $variable) {
|
2025-12-11 09:31:05 +00:00
|
|
|
$name = $variable['name'];
|
|
|
|
|
|
|
|
|
|
$value = $variable['value'];
|
|
|
|
|
$value = \str_replace('{projectName}', $projectName, $value);
|
|
|
|
|
$value = \str_replace('{projectId}', $projectId, $value);
|
|
|
|
|
$value = \str_replace('{apiEndpoint}', 'http://' . System::getEnv('_APP_DOMAIN', '') . '/v1', $value);
|
|
|
|
|
|
|
|
|
|
if(\array_key_exists($name, $variables)) {
|
|
|
|
|
$value = $variables[$name];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty($value)) {
|
2025-02-27 16:46:47 +00:00
|
|
|
if (($variable['required'] ?? false) === true) {
|
2025-12-11 09:31:05 +00:00
|
|
|
throw new \Exception("Missing required variable: {$variable['name']}. Provide it using the --variables flag to resolve this.");
|
2025-02-27 16:28:25 +00:00
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 16:28:25 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 16:28:25 +00:00
|
|
|
$response = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => $projectId,
|
|
|
|
|
'x-appwrite-mode' => 'admin',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
|
|
|
|
'key' => $variable['name'],
|
|
|
|
|
'value' => $value
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($response['headers']['status-code'] !== 201) {
|
2025-02-27 16:28:25 +00:00
|
|
|
Console::error(\json_encode($response));
|
|
|
|
|
throw new \Exception("Failed to create variable");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Console::info("Variables created");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Create deployment
|
|
|
|
|
$deployment = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments/template', [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => $projectId,
|
|
|
|
|
'x-appwrite-mode' => 'admin',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
], [
|
|
|
|
|
'owner' => $template['providerOwner'],
|
|
|
|
|
'repository' => $template['providerRepositoryId'],
|
|
|
|
|
'rootDirectory' => $framework['providerRootDirectory'],
|
2025-12-11 09:31:05 +00:00
|
|
|
'reference' => $template['providerVersion'],
|
|
|
|
|
'type' => 'tag',
|
2025-02-27 14:58:33 +00:00
|
|
|
'activate' => true,
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($deployment['headers']['status-code'] !== 202) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($deployment));
|
|
|
|
|
throw new \Exception("Failed to create deployment");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Deployment created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$deploymentId = $deployment['body']['$id'];
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
// Await screenshot
|
2025-03-11 13:16:27 +00:00
|
|
|
$attempts = 60; // 5 min
|
2025-02-27 14:58:33 +00:00
|
|
|
$sleep = 5;
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$idLight = '';
|
|
|
|
|
$idDark = '';
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-04-22 15:06:01 +00:00
|
|
|
$slowTemplates = [
|
2025-04-22 15:20:21 +00:00
|
|
|
'starter-for-react-native',
|
|
|
|
|
'playground-for-react-native'
|
2025-04-22 15:06:01 +00:00
|
|
|
];
|
|
|
|
|
if (\in_array($templateId, $slowTemplates)) {
|
|
|
|
|
Console::warning("Build for this template is slow, increasing waiting time ...");
|
2025-03-11 13:16:27 +00:00
|
|
|
$attempts = 180; // 15 min
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::log("Awaiting deployment (every $sleep seconds, $attempts attempts)");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
for ($i = 0; $i < $attempts; $i++) {
|
2025-02-27 14:58:33 +00:00
|
|
|
$deployment = $client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId, [
|
|
|
|
|
'content-type' => 'application/json',
|
|
|
|
|
'x-appwrite-project' => $projectId,
|
|
|
|
|
'x-appwrite-mode' => 'admin',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($deployment['headers']['status-code'] !== 200) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($deployment));
|
|
|
|
|
throw new \Exception("Failed to get deployment");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($deployment['body']['status'] === 'failed') {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($deployment));
|
|
|
|
|
throw new \Exception("Deployment build failed");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($deployment['body']['status'] !== 'ready') {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::log("Deployment not ready yet, status: " . $deployment['body']['status']);
|
|
|
|
|
\sleep($sleep);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if (empty($deployment['body']['screenshotLight'])) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::log("Light screenshot not available yet");
|
|
|
|
|
\sleep($sleep);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if (empty($deployment['body']['screenshotDark'])) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::log("Dark screenshot not available yet");
|
|
|
|
|
\sleep($sleep);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$idLight = $deployment['body']['screenshotLight'];
|
|
|
|
|
$idDark = $deployment['body']['screenshotDark'];
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if (empty($idLight) || empty($idDark)) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($deployment));
|
|
|
|
|
throw new \Exception("Failed to get deployment screenshot");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::info("Screenshots created");
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$themes = [
|
|
|
|
|
[ 'fileId' => $idLight, 'suffix' => 'light' ],
|
|
|
|
|
[ 'fileId' => $idDark, 'suffix' => 'dark' ]
|
|
|
|
|
];
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
foreach ($themes as $theme) {
|
2025-02-27 14:58:33 +00:00
|
|
|
$file = $client->call(Client::METHOD_GET, '/storage/buckets/screenshots/files/' . $theme['fileId'] . '/download', [
|
|
|
|
|
'x-appwrite-project' => 'console',
|
|
|
|
|
'cookie' => $cookieConsole
|
|
|
|
|
]);
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if ($file['headers']['status-code'] !== 200) {
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::error(\json_encode($file));
|
|
|
|
|
throw new \Exception("Failed to download {$theme['suffix']} screenshot");
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
$path = "/usr/src/code/public/images/sites/templates/{$template['key']}-{$theme['suffix']}.png";
|
2025-02-27 16:46:47 +00:00
|
|
|
|
|
|
|
|
if (!\file_put_contents($path, $file['body'])) {
|
2025-02-27 14:58:33 +00:00
|
|
|
throw new \Exception("Failed to save {$theme['suffix']} screenshot");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-27 16:46:47 +00:00
|
|
|
|
2025-02-27 14:58:33 +00:00
|
|
|
Console::success("Screenshots saved");
|
|
|
|
|
}
|
|
|
|
|
}
|