fix(app-store): handle orphaned stack directories on template deploy (#530)

* fix(app-store): handle orphaned stack directories on template deploy

When a stack deployed via the App Store is later removed through Docker
Desktop or the CLI (instead of through Sencho), its directory remains on
disk without a compose file. The deploy endpoint previously rejected any
re-deploy with a 409 if the directory existed, even if empty.

Now the endpoint checks for a compose file before rejecting. If the
directory exists but contains no compose file, it is treated as an
orphaned remnant: cleaned up automatically and the deploy proceeds.

Also makes FileSystemService.hasComposeFile public so the deploy
endpoint can reuse it instead of duplicating the compose file check.

* docs(app-store): document orphaned stack directory cleanup behavior
This commit is contained in:
Anso
2026-04-12 19:26:29 -04:00
committed by GitHub
parent 36f2e8a7bc
commit 5f91e16417
3 changed files with 14 additions and 5 deletions
+11 -4
View File
@@ -5687,10 +5687,17 @@ app.post('/api/templates/deploy', authMiddleware, async (req: Request, res: Resp
try {
await fsPromises.access(stackPath);
return res.status(409).json({
error: `A stack directory named '${stackName}' already exists. Please choose a different Stack Name.`,
rolledBack: false
});
if (await fsService.hasComposeFile(stackPath)) {
return res.status(409).json({
error: `A stack directory named '${stackName}' already exists. Please choose a different Stack Name.`,
rolledBack: false
});
}
// Orphaned directory left by external deletion (e.g. Docker Desktop).
console.log(`[Templates] Cleaned up orphaned stack directory: ${stackName}`);
await fsService.deleteStack(stackName);
} catch {
// Directory does not exist; proceed with deploy
}
+1 -1
View File
@@ -34,7 +34,7 @@ export class FileSystemService {
return new FileSystemService(nodeId);
}
private async hasComposeFile(dir: string): Promise<boolean> {
async hasComposeFile(dir: string): Promise<boolean> {
const composeFiles = ['compose.yaml', 'compose.yml', 'docker-compose.yaml', 'docker-compose.yml'];
for (const file of composeFiles) {
try {