From aa6f34c5a853218b8c5d187ff8ed15b267869b07 Mon Sep 17 00:00:00 2001 From: Guilherme Biff Zarelli Date: Fri, 16 Oct 2026 22:40:21 -0300 Subject: [PATCH 1/4] fix(ci): retry transient FTP deploy failures --- .github/workflows/deploy.yml | 70 +++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0fce710..55740e9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -40,42 +40,54 @@ jobs: - name: Deploy Next.js assets (excluding app chunks) if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - uses: SamKirkland/FTP-Deploy-Action@v4.3.6 + uses: Wandalen/wretry.action@v3.8.0 with: - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} - local-dir: ./out/_next/ - server-dir: ./_next/ - state-name: .ftp-deploy-sync-state.json - exclude: | - **/static/chunks/app/** + action: SamKirkland/FTP-Deploy-Action@v4.3.6 + attempt_limit: 3 + attempt_delay: 15000 + with: | + server: ${{ secrets.FTP_HOST }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + local-dir: ./out/_next/ + server-dir: ./_next/ + state-name: .ftp-deploy-sync-state.json + exclude: | + **/static/chunks/app/** - name: Deploy Next.js app chunks if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - uses: SamKirkland/FTP-Deploy-Action@v4.3.6 + uses: Wandalen/wretry.action@v3.8.0 with: - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} - local-dir: ./out/_next/static/chunks/app/ - server-dir: ./_next/static/chunks/app/ - state-name: .ftp-deploy-sync-state.json + action: SamKirkland/FTP-Deploy-Action@v4.3.6 + attempt_limit: 3 + attempt_delay: 15000 + with: | + server: ${{ secrets.FTP_HOST }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + local-dir: ./out/_next/static/chunks/app/ + server-dir: ./_next/static/chunks/app/ + state-name: .ftp-deploy-sync-state.json - name: Deploy site files to FTP if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - uses: SamKirkland/FTP-Deploy-Action@v4.3.6 + uses: Wandalen/wretry.action@v3.8.0 with: - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} - local-dir: ./out/ - server-dir: ./ - exclude: | - **/.git* - **/.git*/** - **/node_modules/** - **/.DS_Store - **/_next/** - dry-run: false + action: SamKirkland/FTP-Deploy-Action@v4.3.6 + attempt_limit: 3 + attempt_delay: 15000 + with: | + server: ${{ secrets.FTP_HOST }} + username: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + local-dir: ./out/ + server-dir: ./ + exclude: | + **/.git* + **/.git*/** + **/node_modules/** + **/.DS_Store + **/_next/** + dry-run: false From ed224e64ff65344287c0e2510edd5fc18e8dfdc3 Mon Sep 17 00:00:00 2001 From: Guilherme Biff Zarelli Date: Fri, 16 Oct 2026 22:43:31 -0300 Subject: [PATCH 2/4] fix(ci): encode FTP secrets in retry inputs --- .github/workflows/deploy.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 55740e9..2344399 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -46,9 +46,9 @@ jobs: attempt_limit: 3 attempt_delay: 15000 with: | - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} + server: ${{ toJSON(secrets.FTP_HOST) }} + username: ${{ toJSON(secrets.FTP_USERNAME) }} + password: ${{ toJSON(secrets.FTP_PASSWORD) }} local-dir: ./out/_next/ server-dir: ./_next/ state-name: .ftp-deploy-sync-state.json @@ -63,9 +63,9 @@ jobs: attempt_limit: 3 attempt_delay: 15000 with: | - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} + server: ${{ toJSON(secrets.FTP_HOST) }} + username: ${{ toJSON(secrets.FTP_USERNAME) }} + password: ${{ toJSON(secrets.FTP_PASSWORD) }} local-dir: ./out/_next/static/chunks/app/ server-dir: ./_next/static/chunks/app/ state-name: .ftp-deploy-sync-state.json @@ -78,9 +78,9 @@ jobs: attempt_limit: 3 attempt_delay: 15000 with: | - server: ${{ secrets.FTP_HOST }} - username: ${{ secrets.FTP_USERNAME }} - password: ${{ secrets.FTP_PASSWORD }} + server: ${{ toJSON(secrets.FTP_HOST) }} + username: ${{ toJSON(secrets.FTP_USERNAME) }} + password: ${{ toJSON(secrets.FTP_PASSWORD) }} local-dir: ./out/ server-dir: ./ exclude: | From 6a40d4e0de82d935aae040adb6778e3c2aa8ef84 Mon Sep 17 00:00:00 2001 From: Guilherme Biff Zarelli Date: Fri, 16 Oct 2026 22:46:31 -0300 Subject: [PATCH 3/4] fix(ci): mask serialized FTP credentials --- .github/workflows/deploy.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2344399..5f8afaf 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,6 +37,24 @@ jobs: ls -la out/ echo "Build output files:" find out/ -type f | head -10 + + - name: Mask serialized FTP credentials + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + env: + FTP_HOST_JSON: ${{ toJSON(secrets.FTP_HOST) }} + FTP_USERNAME_JSON: ${{ toJSON(secrets.FTP_USERNAME) }} + FTP_PASSWORD_JSON: ${{ toJSON(secrets.FTP_PASSWORD) }} + run: | + mask() { + local value="$1" + value="${value//%/%25}" + value="${value//$'\r'/%0D}" + value="${value//$'\n'/%0A}" + printf '::add-mask::%s\n' "$value" + } + mask "$FTP_HOST_JSON" + mask "$FTP_USERNAME_JSON" + mask "$FTP_PASSWORD_JSON" - name: Deploy Next.js assets (excluding app chunks) if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') From 2307fc44a6296662cbb84911f260d5ca3a3df6ec Mon Sep 17 00:00:00 2001 From: Guilherme Biff Zarelli Date: Fri, 16 Oct 2026 22:48:30 -0300 Subject: [PATCH 4/4] fix(ci): mask encoded FTP credentials before retries --- .github/workflows/deploy.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5f8afaf..f5fc504 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,20 +41,22 @@ jobs: - name: Mask serialized FTP credentials if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') env: - FTP_HOST_JSON: ${{ toJSON(secrets.FTP_HOST) }} - FTP_USERNAME_JSON: ${{ toJSON(secrets.FTP_USERNAME) }} - FTP_PASSWORD_JSON: ${{ toJSON(secrets.FTP_PASSWORD) }} + FTP_HOST_RAW: ${{ secrets.FTP_HOST }} + FTP_USERNAME_RAW: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD_RAW: ${{ secrets.FTP_PASSWORD }} run: | - mask() { - local value="$1" - value="${value//%/%25}" - value="${value//$'\r'/%0D}" - value="${value//$'\n'/%0A}" - printf '::add-mask::%s\n' "$value" + node <<'NODE' + const names = ['FTP_HOST_RAW', 'FTP_USERNAME_RAW', 'FTP_PASSWORD_RAW']; + const escapeCommandData = (value) => value + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); + + for (const name of names) { + const serialized = JSON.stringify(process.env[name] ?? ''); + process.stdout.write(`::add-mask::${escapeCommandData(serialized)}\n`); } - mask "$FTP_HOST_JSON" - mask "$FTP_USERNAME_JSON" - mask "$FTP_PASSWORD_JSON" + NODE - name: Deploy Next.js assets (excluding app chunks) if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')