Repository: go-to-k/delstack
Pull request #315 (2024-01-17 23:08:53)

chore(deps): bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/releases">actions/cache's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update action to node20 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1284">actions/cache#1284</a></li>
<li>feat: save-always flag by <a href="https://github.com/to-s"><code>@​to-s</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1242">actions/cache#1242</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1284">actions/cache#1284</a></li>
<li><a href="https://github.com/to-s"><code>@​to-s</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1242">actions/cache#1242</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v3...v4.0.0">https://github.com/actions/cache/compare/v3...v4.0.0</a></p>
<h2>v3.3.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Cache v3.3.3 by <a href="https://github.com/robherley"><code>@​robherley</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1302">actions/cache#1302</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/robherley"><code>@​robherley</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1302">actions/cache#1302</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v3...v3.3.3">https://github.com/actions/cache/compare/v3...v3.3.3</a></p>
<h2>v3.3.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fixed readme with new segment timeout values by <a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1133">actions/cache#1133</a></li>
<li>Readme fixes by <a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1134">actions/cache#1134</a></li>
<li>Updated description of the lookup-only input for main action by <a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1130">actions/cache#1130</a></li>
<li>Change two new actions mention as quoted text by <a href="https://github.com/bishal-pdMSFT"><code>@​bishal-pdMSFT</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1131">actions/cache#1131</a></li>
<li>Update Cross-OS Caching tips by <a href="https://github.com/pdotl"><code>@​pdotl</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1122">actions/cache#1122</a></li>
<li>Bazel example (Take <a href="https://redirect.github.com/actions/cache/issues/2">#2</a>️⃣) by <a href="https://github.com/vorburger"><code>@​vorburger</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1132">actions/cache#1132</a></li>
<li>Remove actions to add new PRs and issues to a project board by <a href="https://github.com/jorendorff"><code>@​jorendorff</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1187">actions/cache#1187</a></li>
<li>Consume latest toolkit and fix dangling promise bug by <a href="https://github.com/chkimes"><code>@​chkimes</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1217">actions/cache#1217</a></li>
<li>Bump action version to 3.3.2 by <a href="https://github.com/bethanyj28"><code>@​bethanyj28</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1236">actions/cache#1236</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/vorburger"><code>@​vorburger</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1132">actions/cache#1132</a></li>
<li><a href="https://github.com/jorendorff"><code>@​jorendorff</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1187">actions/cache#1187</a></li>
<li><a href="https://github.com/chkimes"><code>@​chkimes</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1217">actions/cache#1217</a></li>
<li><a href="https://github.com/bethanyj28"><code>@​bethanyj28</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1236">actions/cache#1236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v3...v3.3.2">https://github.com/actions/cache/compare/v3...v3.3.2</a></p>
<h2>v3.3.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Reduced download segment size to 128 MB and timeout to 10 minutes by <a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1129">actions/cache#1129</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v3...v3.3.1">https://github.com/actions/cache/compare/v3...v3.3.1</a></p>
<h2>v3.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bug: Permission is missing in cache delete example by <a href="https://github.com/kotokaze"><code>@​kotokaze</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1123">actions/cache#1123</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h3>3.0.0</h3>
<ul>
<li>Updated minimum runner version support from node 12 -&gt; node 16</li>
</ul>
<h3>3.0.1</h3>
<ul>
<li>Added support for caching from GHES 3.5.</li>
<li>Fixed download issue for files &gt; 2GB during restore.</li>
</ul>
<h3>3.0.2</h3>
<ul>
<li>Added support for dynamic cache size cap on GHES.</li>
</ul>
<h3>3.0.3</h3>
<ul>
<li>Fixed avoiding empty cache save when no files are available for caching. (<a href="https://redirect.github.com/actions/cache/issues/624">issue</a>)</li>
</ul>
<h3>3.0.4</h3>
<ul>
<li>Fixed tar creation error while trying to create tar with path as <code>~/</code> home folder on <code>ubuntu-latest</code>. (<a href="https://redirect.github.com/actions/cache/issues/689">issue</a>)</li>
</ul>
<h3>3.0.5</h3>
<ul>
<li>Removed error handling by consuming actions/cache 3.0 toolkit, Now cache server error handling will be done by toolkit. (<a href="https://redirect.github.com/actions/cache/pull/834">PR</a>)</li>
</ul>
<h3>3.0.6</h3>
<ul>
<li>Fixed <a href="https://redirect.github.com/actions/cache/issues/809">#809</a> - zstd -d: no such file or directory error</li>
<li>Fixed <a href="https://redirect.github.com/actions/cache/issues/833">#833</a> - cache doesn't work with github workspace directory</li>
</ul>
<h3>3.0.7</h3>
<ul>
<li>Fixed <a href="https://redirect.github.com/actions/cache/issues/810">#810</a> - download stuck issue. A new timeout is introduced in the download process to abort the download if it gets stuck and doesn't finish within an hour.</li>
</ul>
<h3>3.0.8</h3>
<ul>
<li>Fix zstd not working for windows on gnu tar in issues <a href="https://redirect.github.com/actions/cache/issues/888">#888</a> and <a href="https://redirect.github.com/actions/cache/issues/891">#891</a>.</li>
<li>Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable <code>SEGMENT_DOWNLOAD_TIMEOUT_MINS</code>. Default is 60 minutes.</li>
</ul>
<h3>3.0.9</h3>
<ul>
<li>Enhanced the warning message for cache unavailablity in case of GHES.</li>
</ul>
<h3>3.0.10</h3>
<ul>
<li>Fix a bug with sorting inputs.</li>
<li>Update definition for restore-keys in README.md</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/cache/commit/13aacd865c20de90d75de3b17ebe84f7a17d57d2"><code>13aacd8</code></a> Merge pull request <a href="https://redirect.github.com/actions/cache/issues/1242">#1242</a> from to-s/main</li>
<li><a href="https://github.com/actions/cache/commit/53b35c543921fe2e8b288765ff817de9de8d906f"><code>53b35c5</code></a> Merge branch 'main' into main</li>
<li><a href="https://github.com/actions/cache/commit/65b8989fab3bb394817bdb845a453dff480c2b51"><code>65b8989</code></a> Merge pull request <a href="https://redirect.github.com/actions/cache/issues/1284">#1284</a> from takost/update-to-node-20</li>
<li><a href="https://github.com/actions/cache/commit/d0be34d54485f31ca2ccbe66e6ea3d96544a807b"><code>d0be34d</code></a> Fix dist</li>
<li><a href="https://github.com/actions/cache/commit/66cf064d47313d2cccf392d01bd10925da2bd072"><code>66cf064</code></a> Merge branch 'main' into update-to-node-20</li>
<li><a href="https://github.com/actions/cache/commit/1326563738ddb735c5f2ce85cba8c79f33b728cd"><code>1326563</code></a> Merge branch 'main' into main</li>
<li><a href="https://github.com/actions/cache/commit/e71876755e268d6cc25a5d3e3c46ae447e35290a"><code>e718767</code></a> Fix format</li>
<li><a href="https://github.com/actions/cache/commit/01229828ffa049a8dee4db27bcb23ed33f2b451f"><code>0122982</code></a> Apply workaround for earlyExit</li>
<li><a href="https://github.com/actions/cache/commit/3185ecfd6135856ca6d904ae032cff4f39b8b365"><code>3185ecf</code></a> Update &quot;only-&quot; actions to node20</li>
<li><a href="https://github.com/actions/cache/commit/25618a0a675e8447e5ffc8ed9b7ddb2aaf927f65"><code>25618a0</code></a> Bump version</li>
<li>Additional commits viewable in <a href="https://github.com/actions/cache/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f705fb0..637d309 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,7 +28,7 @@ jobs:
           go-version: 1.21
         id: go
       - name: Cache
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: |
             ~/go/pkg/mod
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 7ae2b35..0f894a1 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -34,11 +34,11 @@ changelog:
       - '^docs:'
       - '^test:'
 brews:
-  - tap:
+  - repository:
       owner: go-to-k
       name: homebrew-tap
       token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
-    folder: Formula
+    directory: Formula
     homepage: https://github.com/go-to-k/delstack
     description: delstack
     test: |
diff --git a/README.md b/README.md
index 105bcdc..09c84b6 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ Tool to force delete the **entire** AWS CloudFormation stack, **even if it conta
 
 Among the resources that **fail in the normal CloudFormation stack deletion**, this tool supports the following resources.
 
+If you want to delete the unsupported resources, please create an issue at [GitHub](https://github.com/go-to-k/delstack/issues).
+
 All resources that do not fail normal deletion can be deleted as is.
 
 |  RESOURCE TYPE  |  DETAILS  |
@@ -77,6 +79,9 @@ All resources that do not fail normal deletion can be deleted as is.
   - CloudFormation stack name
     - Must be specified in **not** interactive mode
     - Otherwise you can specify it in the interactive mode
+  - **Multiple specifications are possible.**
+    - `delstack -s test1 -s test2`
+    - **Dependencies between stacks are taken into account, the stacks are deleted in order, starting with the newly created stack.**
 - -p, --profile: optional
   - AWS profile name
 - -r, --region: optional(default: `us-east-1`)
@@ -86,63 +91,63 @@ All resources that do not fail normal deletion can be deleted as is.
 
 ## Interactive Mode
 
-### ResourceTypes
-
-The `-i, --interactive` option allows you to select the ResourceTypes **you wish to force delete even if DELETE_FAILED**. This feature allows you to **protect resources** you really do not want to delete by **"do not select the ResourceTypes"**!
-
-However, if a resource can be deleted **without becoming DELETE_FAILED** by the **normal** CloudFormation stack deletion feature, the resource will be deleted **even if you do not select that resource type**. This tool is not intended to protect specific resources from the normal CloudFormation stack deletion feature, so I implemented this feature with the nuance that **only those resources that really should not be deleted will not be forced to be deleted**.
-
-If the stack contains resources that will be DELETE_FAILED but is not selected, **all DELETE_FAILED resources including the selected or not selected resources and the stack will remain undeleted**.
-
-```bash
-❯ delstack -s YourStack -i
-? Select ResourceTypes you wish to delete even if DELETE_FAILED.
-However, if a resource can be deleted without becoming DELETE_FAILED by the normal CloudFormation stack deletion feature, the resource will be deleted even if you do not select that resource type.
-  [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
-  [ ]  AWS::S3::Bucket
-  [x]  AWS::IAM::Role
-> [x]  AWS::ECR::Repository
-  [ ]  AWS::Backup::BackupVault
-  [x]  AWS::CloudFormation::Stack
-  [ ]  Custom::
-```
-
 ### StackName Selection
 
-If you do not specify a stack name in command options in the interactive mode, you can search stack names in a **case-insensitive** and select a stack.
+If you do not specify a stack name in command options in the interactive mode (`-i, --interactive`), you can search stack names in a **case-insensitive** and select a stack.
 
 It can be **empty**.
 
 ```bash
 ❯ delstack -i
-Filter a keyword of stack names(case-insensitive): test-goto
+Filter a keyword of stack names(case-insensitive): goto
 ```
 
 Then you select stack names in the UI.
 
 ```bash
-? Select StackName.
+? Select StackNames.
 Nested child stacks, XXX_IN_PROGRESS(e.g. ROLLBACK_IN_PROGRESS) status stacks and EnableTerminationProtection stacks are not displayed.
-  [Use arrows to move, type to filter]
-> test-goto-stack-1
-  test-goto-stack-2
-  test-goto-stack-3
-  TEST-GOTO-stack-4
-  Test-Goto-stack-5
-  TEST-goto-stack-6
+  [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
+  [x]  dev-goto-04-TestStack
+  [ ]  dev-GOTO-03-TestStack
+> [x]  dev-Goto-02-TestStack
+  [ ]  dev-goto-01-TestStack
 ```
 
 In addition, **child stacks of nested stacks are not displayed**. This is because it is unlikely that there are cases where only child stacks of nested stacks are deleted without deleting the parent stack, and also because it is possible that the parent stack may be buried in the stack list if there are child stacks, or that the child stacks may be accidentally deleted.
 
-However, the `-s` command option allows deletion of child stacks by specifying their names, so please use this option if you want.
+However, **the `-s` command option allows deletion of CHILD stacks by specifying their names**, so please use this option if you want.
 
 And stacks with **the XXX_IN_PROGRESS(e.g. ROLLBACK_IN_PROGRESS) CloudFormation status** are not displayed, because multiple CloudFormation operations should not be duplicated at the same time.
 
 Also, **"Termination Protection"** stacks will not be displayed, because it probably really should not want to delete it.
 
+### ResourceTypes
+
+The `-i, --interactive` option allows you to select the ResourceTypes **you wish to force delete even if DELETE_FAILED**. This feature allows you to **protect resources** you really do not want to delete by **"do not select the ResourceTypes"**!
+
+However, if a resource can be deleted **without becoming DELETE_FAILED** by the **normal** CloudFormation stack deletion feature, the resource will be deleted **even if you do not select that resource type**. This tool is not intended to protect specific resources from the normal CloudFormation stack deletion feature, so I implemented this feature with the nuance that **only those resources that really should not be deleted will not be forced to be deleted**.
+
+If the stack contains resources that will be DELETE_FAILED but is not selected, **all DELETE_FAILED resources including the selected or not selected resources and the stack will remain undeleted**.
+
+```bash
+❯ delstack -i -s dev-goto-01-TestStack
+? dev-goto-01-TestStack
+Select ResourceTypes you wish to delete even if DELETE_FAILED.
+However, if a resource can be deleted without becoming DELETE_FAILED by the normal CloudFormation stack deletion feature, the resource will be deleted even if you do not select that resource type.
+  [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
+  [x]  AWS::S3::Bucket
+  [ ]  AWS::IAM::Role
+> [x]  AWS::ECR::Repository
+  [ ]  AWS::Backup::BackupVault
+  [ ]  AWS::CloudFormation::Stack
+  [ ]  Custom::
+```
+
 ## GitHub Actions
 
 You can use delstack with parameters **"stack-name" and "region"** in GitHub Actions Workflow.
+To delete multiple stacks, specify stack names separated by commas.
 
 ```yaml
 jobs:
@@ -163,6 +168,7 @@ jobs:
         uses: go-to-k/delstack@main # Or specify the version instead of main
         with:
           stack-name: YourStack
+          # stack-name: YourStack1, YourStack2, YourStack3 # To delete multiple stacks
           region: us-east-1
 ```
 
@@ -187,6 +193,7 @@ jobs:
         uses: go-to-k/delstack@main # Or specify the version instead of main
       - name: Run delstack
         run: |
-          delstack -s YourStack1 -r us-east-1
-          delstack -s YourStack2 -r us-east-1
+          echo "delstack"
+          delstack -v
+          delstack -s YourStack1 -s YourStack2 -r us-east-1
 ```
diff --git a/action.yml b/action.yml
index 51d742a..b83392b 100644
--- a/action.yml
+++ b/action.yml
@@ -6,7 +6,7 @@ branding:
   color: "blue"
 inputs:
   stack-name:
-    description: "Name of the stack you want to delete"
+    description: "Names of one or multiple stacks you want to delete (comma separated)"
     required: false
   region:
     description: "AWS Region"
@@ -29,5 +29,9 @@ runs:
           rm ${FILENAME}
         fi        
         if [ -n "${{ inputs.stack-name }}" ]; then
-          delstack -s ${{ inputs.stack-name }} -r ${{ inputs.region }}
+          stacks=""
+          for stack in $(echo ${{ inputs.stack-name }} | tr ',' ' '); do
+            stacks="${stacks}-s ${stack} "
+          done
+          delstack -r ${{ inputs.region }} $stacks
         fi
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 309e4df..13eb45c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,14 +4,14 @@ go 1.21
 
 require (
 	github.com/AlecAivazis/survey/v2 v2.3.6
-	github.com/aws/aws-sdk-go-v2 v1.23.5
+	github.com/aws/aws-sdk-go-v2 v1.25.2
 	github.com/aws/aws-sdk-go-v2/config v1.25.1
 	github.com/aws/aws-sdk-go-v2/service/backup v1.28.1
-	github.com/aws/aws-sdk-go-v2/service/cloudformation v1.39.1
+	github.com/aws/aws-sdk-go-v2/service/cloudformation v1.47.0
 	github.com/aws/aws-sdk-go-v2/service/ecr v1.22.2
-	github.com/aws/aws-sdk-go-v2/service/iam v1.27.2
+	github.com/aws/aws-sdk-go-v2/service/iam v1.31.1
 	github.com/aws/aws-sdk-go-v2/service/s3 v1.47.3
-	github.com/aws/smithy-go v1.18.1
+	github.com/aws/smithy-go v1.20.1
 	github.com/golang/mock v1.6.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/rs/zerolog v1.30.0
@@ -24,8 +24,8 @@ require (
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.16.1 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.8 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.8 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 // indirect
diff --git a/go.sum b/go.sum
index 308b3d7..68fdcda 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8
 github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
-github.com/aws/aws-sdk-go-v2 v1.23.5 h1:xK6C4udTyDMd82RFvNkDQxtAd00xlzFUtX4fF2nMZyg=
-github.com/aws/aws-sdk-go-v2 v1.23.5/go.mod h1:t3szzKfP0NeRU27uBFczDivYJjsmSnqI8kIvKyWb9ds=
+github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w=
+github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 h1:Zx9+31KyB8wQna6SXFWOewlgoY5uGdDAu6PTOEU3OQI=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3/go.mod h1:zxbEJhRdKTH1nqS2qu6UJ7zGe25xaHxZXaC2CvuQFnA=
 github.com/aws/aws-sdk-go-v2/config v1.25.1 h1:YsjngBOl2mx4l3egkVWndr6/6TqtkdsWJFZIsQ924Ek=
@@ -12,22 +12,22 @@ github.com/aws/aws-sdk-go-v2/credentials v1.16.1 h1:WessyrdgyFN5TB+eLQdrFSlN/3oM
 github.com/aws/aws-sdk-go-v2/credentials v1.16.1/go.mod h1:RQJyPxKcr+m4ArlIG1LUhMOrjposVfzbX6H8oR6oCgE=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4 h1:9wKDWEjwSnXZre0/O3+ZwbBl1SmlgWYBbrTV10X/H1s=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4/go.mod h1:t4i+yGHMCcUNIX1x7YVYa6bH/Do7civ5I6cG/6PMfyA=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.8 h1:8GVZIR0y6JRIUNSYI1xAMF4HDfV8H/bOsZ/8AD/uY5Q=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.8/go.mod h1:rwBfu0SoUkBUZndVgPZKAD9Y2JigaZtRP68unRiYToQ=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.8 h1:ZE2ds/qeBkhk3yqYvS3CDCFNvd9ir5hMjlVStLZWrvM=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.8/go.mod h1:/lAPPymDYL023+TS6DJmjuL42nxix2AvEvfjqOBRODk=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 h1:bNo4LagzUKbjdxE0tIcR9pMzLR2U/Tgie1Hq1HQ3iH8=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2/go.mod h1:wRQv0nN6v9wDXuWThpovGQjqF1HFdcgWjporw14lS8k=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 h1:EtOU5jsPdIQNP+6Q2C5e3d65NKT1PeCiQk+9OdzO12Q=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2/go.mod h1:tyF5sKccmDz0Bv4NrstEr+/9YkSPJHrcO7UsUKf7pWM=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0 h1:usgqiJtamuGIBj+OvYmMq89+Z1hIKkMJToz1WpoeNUY=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.7.0/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
 github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 h1:abKT+RuM1sdCNZIGIfZpLkvxEX3Rpsto019XG/rkYG8=
 github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8/go.mod h1:Owc4ysUE71JSruVTTa3h4f2pp3E4hlcAtmeNXxDmjj4=
 github.com/aws/aws-sdk-go-v2/service/backup v1.28.1 h1:TwcnSp6doyU8V3cBn//J2sQEErFfITihchlh11iTo9k=
 github.com/aws/aws-sdk-go-v2/service/backup v1.28.1/go.mod h1:GpC9a5nFJvQo8/qBD5lSdo/hs3XrwtacujE0kbBSTo0=
-github.com/aws/aws-sdk-go-v2/service/cloudformation v1.39.1 h1:xV9/GIV2eE1BblYbl6cFUN7tGANcMlXQLEs32v5EDTk=
-github.com/aws/aws-sdk-go-v2/service/cloudformation v1.39.1/go.mod h1:NtPc2z+l8sxXmxz0eJebaBY1k1wwZCkXX/UurRbHqV8=
+github.com/aws/aws-sdk-go-v2/service/cloudformation v1.47.0 h1:ohXbAjMKbPzowX7iZCXLwobIT4I+AxP4+o3Q1h0MhVk=
+github.com/aws/aws-sdk-go-v2/service/cloudformation v1.47.0/go.mod h1:xeVzlf/azrN9e9Va3T3MIPTzVH/alM4dlAJO523RMjY=
 github.com/aws/aws-sdk-go-v2/service/ecr v1.22.2 h1:65TcUyXuakCE7kfYY6/gxuPRFjnSh/eMbWkRzG/Aa94=
 github.com/aws/aws-sdk-go-v2/service/ecr v1.22.2/go.mod h1:/ioOZzYo15EL987AAdsmYWKpta8Rokosh5Iax9B6DPg=
-github.com/aws/aws-sdk-go-v2/service/iam v1.27.2 h1:Z3a5I5kKGsuVW4kbrtHVnLGUHpEpo19zFyo6dzP2WCM=
-github.com/aws/aws-sdk-go-v2/service/iam v1.27.2/go.mod h1:CYRyr95Q57xVvrcKJu3vw4jVVCZhmY1SyugM+EWXlzI=
+github.com/aws/aws-sdk-go-v2/service/iam v1.31.1 h1:3l4/wmvUjTbGfk/YJBkKub4cVbDdvJ9YMOQmopXc2T8=
+github.com/aws/aws-sdk-go-v2/service/iam v1.31.1/go.mod h1:EeqEwkHICgkdmzBAJ46zbS4lhvFy563MOuNlEHU59T4=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 h1:e3PCNeEaev/ZF01cQyNZgmYE9oYYePIMJs2mWSKG514=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3/go.mod h1:gIeeNyaL8tIEqZrzAnTeyhHcE0yysCtcaP+N9kxLZ+E=
 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.8 h1:xyfOAYV/ujzZOo01H9+OnyeiRKmTEp6EsITTsmq332Q=
@@ -44,8 +44,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.2 h1:sMAcO7VHVw28HTAdZpTULDzF
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.19.2/go.mod h1:dWqm5G767qwKPuayKfzm4rjzFmVjiBFbOJrpSPnAMDs=
 github.com/aws/aws-sdk-go-v2/service/sts v1.25.2 h1:vwyiRTnXLqsak/6WAQ+uTRhVqKI6vxUQ0HJXjKij0zM=
 github.com/aws/aws-sdk-go-v2/service/sts v1.25.2/go.mod h1:4EqRHDCKP78hq3zOnmFXu5k0j4bXbRFfCh/zQ6KnEfQ=
-github.com/aws/smithy-go v1.18.1 h1:pOdBTUfXNazOlxLrgeYalVnuTpKreACHtc62xLwIB3c=
-github.com/aws/smithy-go v1.18.1/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
+github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
+github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
diff --git a/internal/app/app.go b/internal/app/app.go
index 04ba81d..f4e4d5c 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -15,24 +15,30 @@ import (
 
 type App struct {
 	Cli             *cli.App
-	StackName       string
+	StackNames      *cli.StringSlice
 	Profile         string
 	Region          string
 	InteractiveMode bool
 }
 
+type targetStack struct {
+	stackName           string
+	targetResourceTypes []string
+}
+
 func NewApp(version string) *App {
 	app := App{}
+	app.StackNames = cli.NewStringSlice()
 
 	app.Cli = &cli.App{
 		Name:  "delstack",
 		Usage: "A CLI tool to force delete the entire CloudFormation stack.",
 		Flags: []cli.Flag{
-			&cli.StringFlag{
+			&cli.StringSliceFlag{
 				Name:        "stackName",
 				Aliases:     []string{"s"},
-				Usage:       "CloudFormation stack name",
-				Destination: &app.StackName,
+				Usage:       "CloudFormation stack names(one or more)",
+				Destination: app.StackNames,
 			},
 			&cli.StringFlag{
 				Name:        "profile",
@@ -69,9 +75,9 @@ func (a *App) Run(ctx context.Context) error {
 
 func (a *App) getAction() func(c *cli.Context) error {
 	return func(c *cli.Context) error {
-		if !a.InteractiveMode && a.StackName == "" {
-			errMsg := fmt.Sprintln("The stack name must be specified in command options (-s) or a flow of the interactive mode (-i).")
-			return fmt.Errorf("StackNameNotSpecifiedError: %v", errMsg)
+		if !a.InteractiveMode && len(a.StackNames.Value()) == 0 {
+			errMsg := fmt.Sprintln("At least one stack name must be specified in command options (-s) or a flow of the interactive mode (-i).")
+			return fmt.Errorf("InvalidOptionError: %v", errMsg)
 		}
 
 		config, err := client.LoadAWSConfig(c.Context, a.Region, a.Profile)
@@ -79,70 +85,152 @@ func (a *App) getAction() func(c *cli.Context) error {
 			return err
 		}
 
-		var targetResourceTypes []string
-		var keyword string
-		continuation := true
-		if a.InteractiveMode {
-			targetResourceTypes, keyword, continuation = a.doInteractiveMode()
-		} else {
-			targetResourceTypes = resourcetype.GetResourceTypes()
-		}
+		operatorFactory := operation.NewOperatorFactory(config)
+		cloudformationStackOperator := operatorFactory.CreateCloudFormationStackOperator(resourcetype.GetResourceTypes())
+
+		deduplicatedStackNames := a.deduplicateStackNames()
 
-		if !continuation {
+		sortedStackNames, err := a.getSortedStackNames(c.Context, cloudformationStackOperator, deduplicatedStackNames)
+		if err != nil {
+			return err
+		}
+		// The case for interruption(Ctrl + C)
+		if len(sortedStackNames) == 0 {
 			return nil
 		}
 
-		operatorFactory := operation.NewOperatorFactory(config)
-		cloudformationStackOperator := operatorFactory.CreateCloudFormationStackOperator(targetResourceTypes)
+		targetStacks := a.attachTargetResourceTypes(sortedStackNames, deduplicatedStackNames)
+		// Explanation of deletion order in the case of multiple stacks
+		if len(targetStacks) > 1 {
+			io.Logger.Info().Msg("The stacks are removed in order of the latest creation time, taking into account dependencies.")
+		}
+
+		isRootStack := true
+		for _, stack := range targetStacks {
+			operatorCollection := operation.NewOperatorCollection(config, operatorFactory, stack.targetResourceTypes)
+			operatorManager := operation.NewOperatorManager(operatorCollection)
+			cloudformationStackOperator := operatorFactory.CreateCloudFormationStackOperator(stack.targetResourceTypes)
+
+			io.Logger.Info().Msgf("%v: Start deletion. Please wait a few minutes...", stack.stackName)
 
-		if a.InteractiveMode && a.StackName == "" {
-			stackNames, err := cloudformationStackOperator.ListStacksFilteredByKeyword(c.Context, aws.String(keyword))
-			if err != nil {
+			if err := cloudformationStackOperator.DeleteCloudFormationStack(c.Context, aws.String(stack.stackName), isRootStack, operatorManager); err != nil {
 				return err
 			}
-			if len(stackNames) == 0 {
-				errMsg := fmt.Sprintf("No stacks matching the keyword %s.", keyword)
-				return fmt.Errorf("NotExistsError: %v", errMsg)
-			}
 
-			stackName := a.selectStackName(stackNames)
-			if stackName == "" {
-				return nil
-			}
+			io.Logger.Info().Msgf("%v: Successfully deleted!!", stack.stackName)
+		}
+		return nil
+	}
+}
 
-			a.StackName = stackName
+func (a *App) deduplicateStackNames() []string {
+	deduplicatedStackNames := []string{}
+
+	for _, stackName := range a.StackNames.Value() {
+		var isDuplicated bool
+		for _, deduplicatedStackName := range deduplicatedStackNames {
+			if stackName == deduplicatedStackName {
+				isDuplicated = true
+				break
+			}
 		}
+		if !isDuplicated {
+			deduplicatedStackNames = append(deduplicatedStackNames, stackName)
+		}
+	}
 
-		isRootStack := true
-		operatorCollection := operation.NewOperatorCollection(config, operatorFactory, targetResourceTypes)
-		operatorManager := operation.NewOperatorManager(operatorCollection)
+	return deduplicatedStackNames
+}
 
-		io.Logger.Info().Msgf("Start deletion, %v", a.StackName)
-		io.Logger.Info().Msg("Please wait a few minutes...")
+func (a *App) getSortedStackNames(ctx context.Context, cloudformationStackOperator *operation.CloudFormationStackOperator, specifiedStackNames []string) ([]string, error) {
+	sortedStackNames := []string{}
 
-		if err := cloudformationStackOperator.DeleteCloudFormationStack(c.Context, aws.String(a.StackName), isRootStack, operatorManager); err != nil {
-			return err
+	if len(specifiedStackNames) != 0 {
+		stackNames, err := cloudformationStackOperator.GetSortedStackNames(ctx, specifiedStackNames)
+		if err != nil {
+			return sortedStackNames, err
+		}
+		sortedStackNames = append(sortedStackNames, stackNames...)
+	} else if a.InteractiveMode {
+		keyword := a.inputKeywordForFilter()
+		stacks, err := cloudformationStackOperator.ListStacksFilteredByKeyword(ctx, aws.String(keyword))
+		if err != nil {
+			return sortedStackNames, err
 		}
 
-		io.Logger.Info().Msgf("Successfully deleted, %v", a.StackName)
-		return nil
+		// The `ListStacksFilteredByKeyword` with SDK's `DescribeStacks` returns the stacks in descending order of CreationTime.
+		// Therefore, by deleting stacks in the same order, we can delete from a new stack that is not depended on by any stack.
+		stackNames := a.selectStackNames(stacks)
+		sortedStackNames = append(sortedStackNames, stackNames...)
 	}
+
+	return sortedStackNames, nil
 }
 
-func (a *App) doInteractiveMode() ([]string, string, bool) {
+func (a *App) attachTargetResourceTypes(sortedStackNames []string, specifiedStackNames []string) []targetStack {
+	targetStacks := []targetStack{}
+
+	// If stackNames are specified with an interactive mode option, select ResourceTypes in the order specified (not sorted order).
+	if a.InteractiveMode && len(specifiedStackNames) != 0 {
+		var selectedResourceTypes []targetStack
+		for _, stackName := range specifiedStackNames {
+			targetResourceTypes, continuation := a.selectResourceTypes(stackName)
+			if !continuation {
+				return nil
+			}
+			selectedResourceTypes = append(selectedResourceTypes, targetStack{
+				stackName:           stackName,
+				targetResourceTypes: targetResourceTypes,
+			})
+		}
+		for _, stackName := range sortedStackNames {
+			for _, selectedResourceType := range selectedResourceTypes {
+				if stackName == selectedResourceType.stackName {
+					targetStacks = append(targetStacks, selectedResourceType)
+				}
+			}
+		}
+	}
+	if a.InteractiveMode && len(specifiedStackNames) == 0 {
+		for _, stackName := range sortedStackNames {
+			targetResourceTypes, continuation := a.selectResourceTypes(stackName)
+			if !continuation {
+				return nil
+			}
+			targetStacks = append(targetStacks, targetStack{
+				stackName:           stackName,
+				targetResourceTypes: targetResourceTypes,
+			})
+		}
+	}
+	if !a.InteractiveMode {
+		for _, stackName := range sortedStackNames {
+			targetResourceTypes := resourcetype.GetResourceTypes()
+			targetStacks = append(targetStacks, targetStack{
+				stackName:           stackName,
+				targetResourceTypes: targetResourceTypes,
+			})
+		}
+	}
+
+	return targetStacks
+}
+
+func (a *App) inputKeywordForFilter() string {
+	label := "Filter a keyword of stack names(case-insensitive): "
+	return io.InputKeywordForFilter(label)
+}
+
+func (a *App) selectResourceTypes(stackName string) ([]string, bool) {
 	var checkboxes []string
-	var keyword string
 
-	label := "Select ResourceTypes you wish to delete even if DELETE_FAILED." +
+	label := stackName +
 		"\n" +
+		"Select ResourceTypes you wish to delete even if DELETE_FAILED.\n" +
 		"However, if a resource can be deleted without becoming DELETE_FAILED by the normal CloudFormation stack deletion feature, the resource will be deleted even if you do not select that resource type. " +
 		"\n"
-	opts := resourcetype.GetResourceTypes()
 
-	if a.StackName == "" {
-		stackNameLabel := "Filter a keyword of stack names(case-insensitive): "
-		keyword = io.InputKeywordForFilter(stackNameLabel)
-	}
+	opts := resourcetype.GetResourceTypes()
 
 	for {
 		checkboxes = io.GetCheckboxes(label, opts)
@@ -150,48 +238,48 @@ func (a *App) doInteractiveMode() ([]string, string, bool) {
 		if len(checkboxes) == 0 {
 			ok := io.GetYesNo("No selection?")
 			if ok {
-				return checkboxes, keyword, true
+				return checkboxes, true
 			}
 
 			// The case for interruption(Ctrl + C)
 			ok = io.GetYesNo("Do you want to finish?")
 			if ok {
 				io.Logger.Info().Msg("Finished...")
-				return checkboxes, keyword, false
+				return checkboxes, false
 			}
 			continue
 		}
 
 		ok := io.GetYesNo("OK?")
 		if ok {
-			return checkboxes, keyword, true
+			return checkboxes, true
 		}
 	}
 }
 
-func (a *App) selectStackName(stackNames []string) string {
-	var stackName string
+func (a *App) selectStackNames(stackNames []string) []string {
+	var selectedStackNames []string
 
-	label := "Select StackName." + "\n" +
+	label := "Select StackNames." + "\n" +
 		"Nested child stacks, XXX_IN_PROGRESS(e.g. ROLLBACK_IN_PROGRESS) status stacks and EnableTerminationProtection stacks are not displayed." +
 		"\n"
 
 	for {
-		stackName = io.GetSelection(label, stackNames)
+		selectedStackNames = io.GetCheckboxes(label, stackNames)
 
-		if stackName == "" {
-			io.Logger.Warn().Msg("Select StackName!")
+		if len(selectedStackNames) == 0 {
+			// The case for interruption(Ctrl + C)
 			ok := io.GetYesNo("Do you want to finish?")
 			if ok {
 				io.Logger.Info().Msg("Finished...")
-				return stackName
+				return selectedStackNames
 			}
 			continue
 		}
 
 		ok := io.GetYesNo("OK?")
 		if ok {
-			return stackName
+			return selectedStackNames
 		}
 	}
 }
diff --git a/internal/io/input.go b/internal/io/input.go
index 4de0d6f..d354a59 100644
--- a/internal/io/input.go
+++ b/internal/io/input.go
@@ -15,35 +15,11 @@ func GetCheckboxes(label string, opts []string) []string {
 	res := []string{}
 
 	prompt := &survey.MultiSelect{
-		Message: label,
-		Options: opts,
-	}
-	survey.AskOne(prompt, &res, survey.WithKeepFilter(true))
-
-	return res
-}
-
-func GetSelection(label string, opts []string) string {
-	var res string
-
-	prompt := &survey.Select{
 		Message:  label,
 		Options:  opts,
 		PageSize: SelectionPageSize,
 	}
-	survey.AskOne(
-		prompt,
-		&res,
-		survey.WithFilter(
-			func(filter string, value string, index int) (include bool) {
-				trimmedFilter := strings.TrimSpace(filter)
-				lowerFilter := strings.ToLower(trimmedFilter)
-				lowerValue := strings.ToLower(value)
-
-				return strings.Contains(lowerValue, lowerFilter)
-			},
-		),
-	)
+	survey.AskOne(prompt, &res, survey.WithKeepFilter(true))
 
 	return res
 }
diff --git a/internal/operation/cloudformation_stack.go b/internal/operation/cloudformation_stack.go
index 099e3bf..e80222f 100644
--- a/internal/operation/cloudformation_stack.go
+++ b/internal/operation/cloudformation_stack.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"regexp"
 	"runtime"
+	"sort"
 	"strings"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
@@ -122,7 +123,7 @@ func (o *CloudFormationStackOperator) deleteStackNormally(ctx context.Context, s
 		return false, err
 	}
 	if len(stacksBeforeDelete) == 0 && isRootStack {
-		errMsg := fmt.Sprintf("%s stack not found.", *stackName)
+		errMsg := fmt.Sprintf("%s not found", *stackName)
 		return false, fmt.Errorf("NotExistsError: %v", errMsg)
 	}
 	if len(stacksBeforeDelete) == 0 {
@@ -130,7 +131,7 @@ func (o *CloudFormationStackOperator) deleteStackNormally(ctx context.Context, s
 	}
 
 	if stacksBeforeDelete[0].EnableTerminationProtection != nil && *stacksBeforeDelete[0].EnableTerminationProtection {
-		return false, fmt.Errorf("TerminationProtectionIsEnabled: %v", *stackName)
+		return false, fmt.Errorf("TerminationProtectionError: %v", *stackName)
 	}
 	if o.isExceptedByStackStatus(stacksBeforeDelete[0].StackStatus) {
 		return false, fmt.Errorf("OperationInProgressError: Stacks with XxxInProgress cannot be deleted, but %v: %v", stacksBeforeDelete[0].StackStatus, *stackName)
@@ -145,7 +146,7 @@ func (o *CloudFormationStackOperator) deleteStackNormally(ctx context.Context, s
 		return false, err
 	}
 	if len(stacksAfterDelete) == 0 {
-		io.Logger.Info().Msg("No resources were DELETE_FAILED.")
+		io.Logger.Info().Msgf("%v: No resources were DELETE_FAILED.", *stackName)
 		return true, nil
 	}
 	if stacksAfterDelete[0].StackStatus != "DELETE_FAILED" {
@@ -155,6 +156,73 @@ func (o *CloudFormationStackOperator) deleteStackNormally(ctx context.Context, s
 	return false, nil
 }
 
+func (o *CloudFormationStackOperator) GetSortedStackNames(ctx context.Context, stackNames []string) ([]string, error) {
+	sortedStackNames := []string{}
+	gotStacks := []types.Stack{}
+	notFoundStackNames := []string{}
+	terminationProtectionStackNames := []string{}
+
+	type stackNameInProgress struct {
+		stackName   string
+		stackStatus types.StackStatus
+	}
+	stackNamesInProgress := []stackNameInProgress{}
+
+	for _, stackName := range stackNames {
+		stack, err := o.client.DescribeStacks(ctx, aws.String(stackName))
+		if err != nil {
+			return sortedStackNames, err
+		}
+
+		if len(stack) == 0 {
+			notFoundStackNames = append(notFoundStackNames, stackName)
+			continue
+		}
+
+		// except the stacks with EnableTerminationProtection
+		if stack[0].EnableTerminationProtection != nil && *stack[0].EnableTerminationProtection {
+			terminationProtectionStackNames = append(terminationProtectionStackNames, stackName)
+			continue
+		}
+
+		// except the stacks that are in the exception list
+		if o.isExceptedByStackStatus(stack[0].StackStatus) {
+			stackNamesInProgress = append(stackNamesInProgress, stackNameInProgress{
+				stackName:   stackName,
+				stackStatus: stack[0].StackStatus,
+			})
+			continue
+		}
+
+		gotStacks = append(gotStacks, stack[0]) // DescribeStacks returns a stack with a single element
+	}
+
+	if len(notFoundStackNames) > 0 {
+		errMsg := fmt.Sprintf("%s not found", strings.Join(notFoundStackNames, ", "))
+		return sortedStackNames, fmt.Errorf("NotExistsError: %v", errMsg)
+	}
+	if len(terminationProtectionStackNames) > 0 {
+		return sortedStackNames, fmt.Errorf("TerminationProtectionError: %v", strings.Join(terminationProtectionStackNames, ", "))
+	}
+	if len(stackNamesInProgress) > 0 {
+		var stackNamesWithStatus []string
+		for _, stack := range stackNamesInProgress {
+			stackNamesWithStatus = append(stackNamesWithStatus, fmt.Sprintf("%s: %s", stack.stackStatus, stack.stackName))
+		}
+		errMsg := fmt.Sprintf("Stacks with XxxInProgress cannot be deleted, but %s", strings.Join(stackNamesWithStatus, ", "))
+		return sortedStackNames, fmt.Errorf("OperationInProgressError: %v", errMsg)
+	}
+
+	// Sort gotStacks in descending order by stack.CreationTime
+	sort.Slice(gotStacks, func(i, j int) bool {
+		return gotStacks[i].CreationTime.After(*gotStacks[j].CreationTime)
+	})
+	for _, stack := range gotStacks {
+		sortedStackNames = append(sortedStackNames, *stack.StackName)
+	}
+	return sortedStackNames, nil
+}
+
 func (o *CloudFormationStackOperator) ListStacksFilteredByKeyword(ctx context.Context, keyword *string) ([]string, error) {
 	filteredStacks := []string{}
 
@@ -189,6 +257,11 @@ func (o *CloudFormationStackOperator) ListStacksFilteredByKeyword(ctx context.Co
 		}
 	}
 
+	if len(filteredStacks) == 0 {
+		errMsg := fmt.Sprintf("No stacks matching the keyword (%s)", *keyword)
+		return filteredStacks, fmt.Errorf("NotExistsError: %v", errMsg)
+	}
+
 	return filteredStacks, nil
 }
 
diff --git a/internal/operation/operator_collection.go b/internal/operation/operator_collection.go
index bc71a0f..ec8f0b9 100644
--- a/internal/operation/operator_collection.go
+++ b/internal/operation/operator_collection.go
@@ -121,7 +121,9 @@ func (c *OperatorCollection) RaiseUnsupportedResourceError() error {
 	}
 	supportedStackResources := "\nSupported resources for force deletion of DELETE_FAILED resources are followings.\n" + *io.ToStringAsTableFormat(supportedStackResourcesHeader, supportedStackResourcesData)
 
-	unsupportedResourceError := title + unsupportedStackResources + supportedStackResources
+	issueLink := "\nIf you want to delete the unsupported resources, please create an issue at GitHub(https://github.com/go-to-k/delstack/issues).\n"
+
+	unsupportedResourceError := title + unsupportedStackResources + supportedStackResources + issueLink
 
 	return fmt.Errorf("UnsupportedResourceError: %v", unsupportedResourceError)
 }


Test patch:
diff --git a/internal/operation/cloudformation_stack_test.go b/internal/operation/cloudformation_stack_test.go
index 97a60ff..d1e7b8e 100644
--- a/internal/operation/cloudformation_stack_test.go
+++ b/internal/operation/cloudformation_stack_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 	"testing"
+	"time"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
@@ -114,7 +115,7 @@ func TestCloudFormationStackOperator_DeleteCloudFormationStack(t *testing.T) {
 				)
 			},
 			prepareMockOperatorManagerFn: func(m *MockIOperatorManager) {},
-			want:                         fmt.Errorf("TerminationProtectionIsEnabled: test"),
+			want:                         fmt.Errorf("TerminationProtectionError: test"),
 			wantErr:                      true,
 		},
 		{
@@ -137,7 +138,7 @@ func TestCloudFormationStackOperator_DeleteCloudFormationStack(t *testing.T) {
 				)
 			},
 			prepareMockOperatorManagerFn: func(m *MockIOperatorManager) {},
-			want:                         fmt.Errorf("TerminationProtectionIsEnabled: test"),
+			want:                         fmt.Errorf("TerminationProtectionError: test"),
 			wantErr:                      true,
 		},
 		{
@@ -288,7 +289,7 @@ func TestCloudFormationStackOperator_DeleteCloudFormationStack(t *testing.T) {
 				)
 			},
 			prepareMockOperatorManagerFn: func(m *MockIOperatorManager) {},
-			want:                         fmt.Errorf("NotExistsError: test stack not found."),
+			want:                         fmt.Errorf("NotExistsError: test not found"),
 			wantErr:                      true,
 		},
 		{
@@ -1043,7 +1044,7 @@ func TestCloudFormationStackOperator_deleteStackNormally(t *testing.T) {
 			},
 			want: want{
 				got: false,
-				err: fmt.Errorf("TerminationProtectionIsEnabled: test"),
+				err: fmt.Errorf("TerminationProtectionError: test"),
 			},
 			wantErr: true,
 		},
@@ -1068,7 +1069,7 @@ func TestCloudFormationStackOperator_deleteStackNormally(t *testing.T) {
 			},
 			want: want{
 				got: false,
-				err: fmt.Errorf("TerminationProtectionIsEnabled: test"),
+				err: fmt.Errorf("TerminationProtectionError: test"),
 			},
 			wantErr: true,
 		},
@@ -1233,7 +1234,7 @@ func TestCloudFormationStackOperator_deleteStackNormally(t *testing.T) {
 			},
 			want: want{
 				got: false,
-				err: fmt.Errorf("NotExistsError: test stack not found."),
+				err: fmt.Errorf("NotExistsError: test not found"),
 			},
 			wantErr: true,
 		},
@@ -1444,6 +1445,702 @@ func TestCloudFormationStackOperator_deleteStackNormally(t *testing.T) {
 	}
 }
 
+func TestCloudFormationStackOperator_GetSortedStackNames(t *testing.T) {
+	ctx := context.Background()
+
+	type args struct {
+		ctx        context.Context
+		stackNames []string
+	}
+
+	type want struct {
+		sortedStackNames []string
+		err              error
+	}
+
+	cases := []struct {
+		name                        string
+		args                        args
+		prepareMockCloudFormationFn func(m *client.MockICloudFormation)
+		want                        want
+		wantErr                     bool
+	}{
+		{
+			name: "sort stacks with ascending order successfully",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack4"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{"Stack5", "Stack4", "Stack3", "Stack2", "Stack1"},
+				err:              nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "sort stacks with descending order successfully",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack5", "Stack4", "Stack3", "Stack2", "Stack1"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack4"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{"Stack5", "Stack4", "Stack3", "Stack2", "Stack1"},
+				err:              nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "sort stacks with random order successfully",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack3", "Stack4", "Stack1", "Stack2", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack4"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{"Stack5", "Stack4", "Stack3", "Stack2", "Stack1"},
+				err:              nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "sort stacks failure for non existent stacks",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("NotExistsError: Stack2, Stack4 not found"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for EnableTerminationProtection stacks",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("Stack2"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							CreationTime:                aws.Time(time.Now().Add(-30 * time.Minute)),
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("Stack4"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							CreationTime:                aws.Time(time.Now().Add(-10 * time.Minute)),
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("TerminationProtectionError: Stack2, Stack4"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for stacks in progress",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusUpdateInProgress,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack4"),
+							StackStatus:  types.StackStatusRollbackInProgress,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("OperationInProgressError: Stacks with XxxInProgress cannot be deleted, but UPDATE_IN_PROGRESS: Stack2, ROLLBACK_IN_PROGRESS: Stack4"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for stacks with some errors (not found, termination protection, and excepted status)",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusUpdateInProgress,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("Stack3"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							CreationTime:                aws.Time(time.Now().Add(-20 * time.Minute)),
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("NotExistsError: Stack4 not found"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for stacks with some errors (not found and excepted status)",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusUpdateInProgress,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack3"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-20 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("NotExistsError: Stack4 not found"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for stacks with some errors (not found and termination protection)",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("Stack3"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							CreationTime:                aws.Time(time.Now().Add(-20 * time.Minute)),
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("NotExistsError: Stack4 not found"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for stacks with some errors (termination protection and excepted status)",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2", "Stack3", "Stack4", "Stack5"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-40 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack2"),
+							StackStatus:  types.StackStatusUpdateInProgress,
+							CreationTime: aws.Time(time.Now().Add(-30 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack3")).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("Stack3"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							CreationTime:                aws.Time(time.Now().Add(-20 * time.Minute)),
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack4")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack4"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack5")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack5"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now()),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("TerminationProtectionError: Stack3"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "sort stacks failure for DescribeStacksError",
+			args: args{
+				ctx:        ctx,
+				stackNames: []string{"Stack1", "Stack2"},
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack1")).Return(
+					[]types.Stack{
+						{
+							StackName:    aws.String("Stack1"),
+							StackStatus:  types.StackStatusCreateComplete,
+							CreationTime: aws.Time(time.Now().Add(-10 * time.Minute)),
+						},
+					},
+					nil,
+				)
+				m.EXPECT().DescribeStacks(gomock.Any(), aws.String("Stack2")).Return([]types.Stack{}, fmt.Errorf("DescribeStacksError"))
+			},
+			want: want{
+				sortedStackNames: []string{},
+				err:              fmt.Errorf("DescribeStacksError"),
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			ctrl := gomock.NewController(t)
+			cloudformationMock := client.NewMockICloudFormation(ctrl)
+
+			tt.prepareMockCloudFormationFn(cloudformationMock)
+
+			targetResourceTypes := []string{
+				"AWS::S3::Bucket",
+				"AWS::IAM::Role",
+				"AWS::ECR::Repository",
+				"AWS::Backup::BackupVault",
+				"AWS::CloudFormation::Stack",
+				"Custom::",
+			}
+
+			cloudformationStackOperator := NewCloudFormationStackOperator(aws.Config{}, cloudformationMock, targetResourceTypes)
+
+			output, err := cloudformationStackOperator.GetSortedStackNames(tt.args.ctx, tt.args.stackNames)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("error = %#v, wantErr %#v", err.Error(), tt.wantErr)
+				return
+			}
+			if tt.wantErr && err.Error() != tt.want.err.Error() {
+				t.Errorf("err = %#v, want %#v", err.Error(), tt.want.err.Error())
+				return
+			}
+			if !reflect.DeepEqual(output, tt.want.sortedStackNames) {
+				t.Errorf("output = %#v, want %#v", output, tt.want.sortedStackNames)
+			}
+		})
+	}
+}
+
 func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 	io.NewLogger(false)
 	ctx := context.Background()
@@ -1526,7 +2223,7 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			wantErr: false,
 		},
 		{
-			name: "list stacks with RootId filtered by keyword and no stacks do not have RootId found successfully",
+			name: "list stacks with RootId filtered by keyword and no stacks do not have RootId failure",
 			args: args{
 				ctx:     ctx,
 				keyword: "TestStack",
@@ -1550,9 +2247,9 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			want: want{
 				filteredStacks: []string{},
-				err:            nil,
+				err:            fmt.Errorf("NotExistsError: No stacks matching the keyword (TestStack)"),
 			},
-			wantErr: false,
+			wantErr: true,
 		},
 		{
 			name: "list stacks filtered by lower keyword successfully",
@@ -1645,7 +2342,7 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			wantErr: false,
 		},
 		{
-			name: "list stacks filtered by keyword but no stacks found successfully",
+			name: "list stacks filtered by keyword but no stacks failure",
 			args: args{
 				ctx:     ctx,
 				keyword: "TestStack",
@@ -1655,12 +2352,12 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			want: want{
 				filteredStacks: []string{},
-				err:            nil,
+				err:            fmt.Errorf("NotExistsError: No stacks matching the keyword (TestStack)"),
 			},
-			wantErr: false,
+			wantErr: true,
 		},
 		{
-			name: "list stacks filtered by keyword but empty keyword and no stacks found successfully",
+			name: "list stacks filtered by keyword but empty keyword and no stacks failure",
 			args: args{
 				ctx:     ctx,
 				keyword: "",
@@ -1670,9 +2367,9 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			want: want{
 				filteredStacks: []string{},
-				err:            nil,
+				err:            fmt.Errorf("NotExistsError: No stacks matching the keyword ()"),
 			},
-			wantErr: false,
+			wantErr: true,
 		},
 		{
 			name: "list stacks filtered by keyword failure for describe stacks errors",
@@ -1705,7 +2402,7 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			wantErr: true,
 		},
 		{
-			name: "list stacks filtered by keyword with XxxInProgress stacks successfully",
+			name: "list stacks filtered by keyword with XxxInProgress stacks failure",
 			args: args{
 				ctx:     ctx,
 				keyword: "TestStack",
@@ -1727,9 +2424,9 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			want: want{
 				filteredStacks: []string{},
-				err:            nil,
+				err:            fmt.Errorf("NotExistsError: No stacks matching the keyword (TestStack)"),
 			},
-			wantErr: false,
+			wantErr: true,
 		},
 		{
 			name: "list stacks filtered by keyword with EnableTerminationProtection stacks successfully",
@@ -1762,6 +2459,35 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "list stacks filtered by keyword with EnableTerminationProtection stacks but empty failure",
+			args: args{
+				ctx:     ctx,
+				keyword: "TestStack",
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().DescribeStacks(gomock.Any(), nil).Return(
+					[]types.Stack{
+						{
+							StackName:                   aws.String("TestStack1"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							EnableTerminationProtection: aws.Bool(true),
+						},
+						{
+							StackName:                   aws.String("TestStack2"),
+							StackStatus:                 types.StackStatusCreateComplete,
+							EnableTerminationProtection: aws.Bool(true),
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				filteredStacks: []string{},
+				err:            fmt.Errorf("NotExistsError: No stacks matching the keyword (TestStack)"),
+			},
+			wantErr: true,
+		},
 	}
 
 	for _, tt := range cases {