From b9b1ec1b64c6a073d97eb5f63364c60c9c86cd03 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 26 Feb 2026 07:49:58 +0000 Subject: [PATCH 1/7] fix: respect include inactive deployments option Use the resources array to determine whether to export only the active deployment or all deployments. When deployment/site-deployment resource type is included, export all deployments instead of only the active one. --- src/Migration/Sources/Appwrite.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 47a0346..a2edd3a 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1518,9 +1518,8 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void } try { - if (\in_array(Resource::TYPE_DEPLOYMENT, $resources)) { - $this->exportDeployments($batchSize, true); - } + $exportOnlyActive = !\in_array(Resource::TYPE_DEPLOYMENT, $resources); + $this->exportDeployments($batchSize, $exportOnlyActive); } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_DEPLOYMENT, @@ -1549,9 +1548,8 @@ protected function exportGroupSites(int $batchSize, array $resources): void } try { - if (\in_array(Resource::TYPE_SITE_DEPLOYMENT, $resources)) { - $this->exportSiteDeployments($batchSize, true); - } + $exportOnlyActive = !\in_array(Resource::TYPE_SITE_DEPLOYMENT, $resources); + $this->exportSiteDeployments($batchSize, $exportOnlyActive); } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_SITE_DEPLOYMENT, From 336358d7f376c750cfc470206b19f9e9dd36fab6 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 26 Feb 2026 10:39:05 +0000 Subject: [PATCH 2/7] fix: convert activate param to string for multipart form-data PHP booleans become '1'/'' in multipart/form-data but the API expects 'true'/'false' strings. The non-chunked function deployment path was already correct; this fixes the chunked function deployment and both site deployment paths. --- src/Migration/Destinations/Appwrite.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 6b730a1..a128c73 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1508,7 +1508,7 @@ private function importDeployment(Deployment $deployment): Resource [ 'functionId' => $functionId, 'code' => new \CURLFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), - 'activate' => $deployment->getActivated(), + 'activate' => $deployment->getActivated() ? 'true' : 'false', 'entrypoint' => $deployment->getEntrypoint(), ] ); @@ -1686,7 +1686,7 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource [ 'siteId' => $siteId, 'code' => new \CURLFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), - 'activate' => $deployment->getActivated(), + 'activate' => $deployment->getActivated() ? 'true' : 'false', ] ); @@ -1710,7 +1710,7 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource [ 'siteId' => $siteId, 'code' => new \CURLFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), - 'activate' => $deployment->getActivated(), + 'activate' => $deployment->getActivated() ? 'true' : 'false', ] ); From b827043cb7d799f357f37f1a4c98e9fa3e0a3ed8 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 2 Mar 2026 09:11:44 +0000 Subject: [PATCH 3/7] fix: skip deployment import when parent resource already exists in destination When a function or site already exists in the destination, the API returns 409 and the parent resource is marked as error. Previously, deployments for that parent would still be imported, creating duplicates. Now importDeployment and importSiteDeployment check the parent status and throw an error if it failed. --- src/Migration/Destinations/Appwrite.php | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index a128c73..8c6281a 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1473,7 +1473,18 @@ public function importFunctionResource(Resource $resource): Resource */ private function importDeployment(Deployment $deployment): Resource { - $functionId = $deployment->getFunction()->getId(); + $function = $deployment->getFunction(); + + if ($function->getStatus() === Resource::STATUS_ERROR) { + throw new Exception( + resourceName: $deployment->getName(), + resourceGroup: $deployment->getGroup(), + resourceId: $deployment->getId(), + message: 'Function "' . $function->getId() . '" already exists in destination', + ); + } + + $functionId = $function->getId(); $response = null; @@ -1674,7 +1685,18 @@ public function importSiteResource(Resource $resource): Resource */ private function importSiteDeployment(SiteDeployment $deployment): Resource { - $siteId = $deployment->getSite()->getId(); + $site = $deployment->getSite(); + + if ($site->getStatus() === Resource::STATUS_ERROR) { + throw new Exception( + resourceName: $deployment->getName(), + resourceGroup: $deployment->getGroup(), + resourceId: $deployment->getId(), + message: 'Site "' . $site->getId() . '" already exists in destination', + ); + } + + $siteId = $site->getId(); if ($deployment->getSize() <= Transfer::STORAGE_MAX_CHUNK_SIZE) { $response = $this->client->call( From 928e7c8f40972747b61b4e8608d90ec6a1b82556 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 2 Mar 2026 09:38:02 +0000 Subject: [PATCH 4/7] fix: skip child resource import when parent resource failed --- src/Migration/Destinations/Appwrite.php | 30 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8c6281a..b9a2527 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1451,8 +1451,19 @@ public function importFunctionResource(Resource $resource): Resource break; case Resource::TYPE_ENVIRONMENT_VARIABLE: /** @var EnvVar $resource */ + $function = $resource->getFunc(); + + if ($function->getStatus() === Resource::STATUS_ERROR) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Parent function "' . $function->getId() . '" failed to import', + ); + } + $this->functions->createVariable( - $resource->getFunc()->getId(), + $function->getId(), $resource->getKey(), $resource->getValue() ); @@ -1480,7 +1491,7 @@ private function importDeployment(Deployment $deployment): Resource resourceName: $deployment->getName(), resourceGroup: $deployment->getGroup(), resourceId: $deployment->getId(), - message: 'Function "' . $function->getId() . '" already exists in destination', + message: 'Parent function "' . $function->getId() . '" failed to import', ); } @@ -1663,8 +1674,19 @@ public function importSiteResource(Resource $resource): Resource break; case Resource::TYPE_SITE_VARIABLE: /** @var SiteEnvVar $resource */ + $site = $resource->getSite(); + + if ($site->getStatus() === Resource::STATUS_ERROR) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Parent site "' . $site->getId() . '" failed to import', + ); + } + $this->sites->createVariable( - $resource->getSite()->getId(), + $site->getId(), $resource->getKey(), $resource->getValue() ); @@ -1692,7 +1714,7 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource resourceName: $deployment->getName(), resourceGroup: $deployment->getGroup(), resourceId: $deployment->getId(), - message: 'Site "' . $site->getId() . '" already exists in destination', + message: 'Parent site "' . $site->getId() . '" failed to import', ); } From 541efc80b83908025be17cef2280be1bbd72dde4 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 3 Mar 2026 05:59:16 +0000 Subject: [PATCH 5/7] fix: skip deployments instead of throwing when parent failed, remove unnecessary env var checks --- src/Migration/Destinations/Appwrite.php | 48 +++++++------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index b9a2527..d44abf1 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1451,19 +1451,8 @@ public function importFunctionResource(Resource $resource): Resource break; case Resource::TYPE_ENVIRONMENT_VARIABLE: /** @var EnvVar $resource */ - $function = $resource->getFunc(); - - if ($function->getStatus() === Resource::STATUS_ERROR) { - throw new Exception( - resourceName: $resource->getName(), - resourceGroup: $resource->getGroup(), - resourceId: $resource->getId(), - message: 'Parent function "' . $function->getId() . '" failed to import', - ); - } - $this->functions->createVariable( - $function->getId(), + $resource->getFunc()->getId(), $resource->getKey(), $resource->getValue() ); @@ -1486,13 +1475,12 @@ private function importDeployment(Deployment $deployment): Resource { $function = $deployment->getFunction(); + // Deployment API always creates a new deployment, so unlike other resources + // there's no duplicate detection. Skip if the parent function failed to import. if ($function->getStatus() === Resource::STATUS_ERROR) { - throw new Exception( - resourceName: $deployment->getName(), - resourceGroup: $deployment->getGroup(), - resourceId: $deployment->getId(), - message: 'Parent function "' . $function->getId() . '" failed to import', - ); + $deployment->setStatus(Resource::STATUS_SKIPPED, 'Parent function "' . $function->getId() . '" failed to import'); + + return $deployment; } $functionId = $function->getId(); @@ -1674,19 +1662,8 @@ public function importSiteResource(Resource $resource): Resource break; case Resource::TYPE_SITE_VARIABLE: /** @var SiteEnvVar $resource */ - $site = $resource->getSite(); - - if ($site->getStatus() === Resource::STATUS_ERROR) { - throw new Exception( - resourceName: $resource->getName(), - resourceGroup: $resource->getGroup(), - resourceId: $resource->getId(), - message: 'Parent site "' . $site->getId() . '" failed to import', - ); - } - $this->sites->createVariable( - $site->getId(), + $resource->getSite()->getId(), $resource->getKey(), $resource->getValue() ); @@ -1709,13 +1686,12 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource { $site = $deployment->getSite(); + // Deployment API always creates a new deployment, so unlike other resources + // there's no duplicate detection. Skip if the parent site failed to import. if ($site->getStatus() === Resource::STATUS_ERROR) { - throw new Exception( - resourceName: $deployment->getName(), - resourceGroup: $deployment->getGroup(), - resourceId: $deployment->getId(), - message: 'Parent site "' . $site->getId() . '" failed to import', - ); + $deployment->setStatus(Resource::STATUS_SKIPPED, 'Parent site "' . $site->getId() . '" failed to import'); + + return $deployment; } $siteId = $site->getId(); From f27276bb8b35142e01aa4bd32f45b090ad680a25 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 3 Mar 2026 06:27:17 +0000 Subject: [PATCH 6/7] fix: check parent status !== SUCCESS instead of === ERROR for extensibility --- src/Migration/Destinations/Appwrite.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index d44abf1..98a4e94 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1476,8 +1476,8 @@ private function importDeployment(Deployment $deployment): Resource $function = $deployment->getFunction(); // Deployment API always creates a new deployment, so unlike other resources - // there's no duplicate detection. Skip if the parent function failed to import. - if ($function->getStatus() === Resource::STATUS_ERROR) { + // there's no duplicate detection. Skip if the parent function wasn't imported successfully. + if ($function->getStatus() !== Resource::STATUS_SUCCESS) { $deployment->setStatus(Resource::STATUS_SKIPPED, 'Parent function "' . $function->getId() . '" failed to import'); return $deployment; @@ -1687,8 +1687,8 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource $site = $deployment->getSite(); // Deployment API always creates a new deployment, so unlike other resources - // there's no duplicate detection. Skip if the parent site failed to import. - if ($site->getStatus() === Resource::STATUS_ERROR) { + // there's no duplicate detection. Skip if the parent site wasn't imported successfully. + if ($site->getStatus() !== Resource::STATUS_SUCCESS) { $deployment->setStatus(Resource::STATUS_SKIPPED, 'Parent site "' . $site->getId() . '" failed to import'); return $deployment; From 0766cd935a008d17a1ee8ee7fdbfd58198b62e2a Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 3 Mar 2026 10:40:07 +0000 Subject: [PATCH 7/7] fix: skip paginated export for functions/sites with no active deployment When exportOnlyActive is true, functions/sites without an active deployment ID should be skipped entirely rather than falling back to the full paginated listing. --- src/Migration/Sources/Appwrite.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index a2edd3a..2844bcf 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1641,8 +1641,13 @@ private function exportDeployments(int $batchSize, bool $exportOnlyActive = fals /** @var Func $func */ $lastDocument = null; - if ($exportOnlyActive && $func->getActiveDeployment()) { - $deployment = $this->functions->getDeployment($func->getId(), $func->getActiveDeployment()); + if ($exportOnlyActive) { + $activeDeploymentId = $func->getActiveDeployment(); + if (empty($activeDeploymentId)) { + continue; // active-only mode: nothing to export for this function + } + + $deployment = $this->functions->getDeployment($func->getId(), $activeDeploymentId); try { $this->exportDeploymentData($func, $deployment); @@ -1863,8 +1868,13 @@ private function exportSiteDeployments(int $batchSize, bool $exportOnlyActive = /** @var Site $site */ $lastDocument = null; - if ($exportOnlyActive && $site->getActiveDeployment()) { - $deployment = $this->sites->getDeployment($site->getId(), $site->getActiveDeployment()); + if ($exportOnlyActive) { + $activeDeploymentId = $site->getActiveDeployment(); + if (empty($activeDeploymentId)) { + continue; // active-only mode: nothing to export for this site + } + + $deployment = $this->sites->getDeployment($site->getId(), $activeDeploymentId); try { $this->exportSiteDeploymentData($site, $deployment);