From fc67e7cf0ba3ca290513b95328d99b9792145653 Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Thu, 23 Jul 2026 00:00:48 +0200 Subject: [PATCH] Fix idempotent FTPS directory setup (#217) The production FTPS deploy now authenticates, but lftp exits when cPanel returns 550 File exists for pre-created deployment directories. Use lftp mkdir -p -f for the archive and activation directories so retries remain idempotent while subsequent upload operations still surface real access failures.\n\nVerification:\n- vitest tests/unit/cpanel-deploy.spec.js (26 passed)\n- eslint scripts/release/cpanel-deploy-lib.mjs tests/unit/cpanel-deploy.spec.js\n- git diff --check Co-authored-by: Jeppe Bundgaard --- scripts/release/cpanel-deploy-lib.mjs | 10 +++++++--- tests/unit/cpanel-deploy.spec.js | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/release/cpanel-deploy-lib.mjs b/scripts/release/cpanel-deploy-lib.mjs index 847b8eae..5531fdeb 100644 --- a/scripts/release/cpanel-deploy-lib.mjs +++ b/scripts/release/cpanel-deploy-lib.mjs @@ -247,6 +247,10 @@ function lftpQuote(value) { return `'${String(value).replaceAll("'", `'\\''`)}'`; } +function ensureLftpDirectory(value) { + return `mkdir -p -f ${lftpQuote(value)}`; +} + export function buildLftpScript(config, commands) { const { host, user, password, root } = config.ftp; const lines = [ @@ -498,8 +502,8 @@ export function createLftpTransport(config, dependencies = {}) { let queueError; try { await run([ - `mkdir -p ${lftpQuote("activation-requests")}`, - `mkdir -p ${lftpQuote("activation-results")}`, + ensureLftpDirectory("activation-requests"), + ensureLftpDirectory("activation-results"), `rm -f ${lftpQuote(`${remoteRequest}.part`)}`, `put ${lftpQuote(requestPath)} -o ${lftpQuote(`${remoteRequest}.part`)}`, `mv ${lftpQuote(`${remoteRequest}.part`)} ${lftpQuote(remoteRequest)}`, @@ -557,7 +561,7 @@ export function createLftpTransport(config, dependencies = {}) { const downloadedChecksum = path.join(temporaryDirectory, checksumName); try { await run([ - `mkdir -p ${lftpQuote("archives")}`, + ensureLftpDirectory("archives"), `rm -f ${lftpQuote(`archives/${archiveName}.part`)}`, `rm -f ${lftpQuote(`archives/${checksumName}.part`)}`, `put ${lftpQuote(config.archivePath)} -o ${lftpQuote(`archives/${archiveName}.part`)}`, diff --git a/tests/unit/cpanel-deploy.spec.js b/tests/unit/cpanel-deploy.spec.js index f581230e..c0196e60 100644 --- a/tests/unit/cpanel-deploy.spec.js +++ b/tests/unit/cpanel-deploy.spec.js @@ -248,6 +248,7 @@ describe("secure FTPS archive upload", () => { }); expect(calls).toHaveLength(2); expect(calls[0].args).toEqual([]); + expect(calls[0].input).toContain("mkdir -p -f 'archives'"); expect(calls[0].input).toContain(`${path.basename(archivePath)}.part`); expect(calls[0].input).toContain("get 'archives/"); expect(calls[1].input).toContain( @@ -303,6 +304,8 @@ describe("secure FTPS archive upload", () => { target ); expect(scripts[0]).not.toContain("release-activate.sh"); + expect(scripts[0]).toContain("mkdir -p -f 'activation-requests'"); + expect(scripts[0]).toContain("mkdir -p -f 'activation-results'"); expect(uploadedRequest).toContain(`release_id=${deploymentConfig.releaseId}`); expect(uploadedRequest).toContain(`commit_sha=${deploymentConfig.expectedCommit}`); expect(uploadedRequest).toContain("archive_sha256=" + "a".repeat(64));