Automate Front‑End Asset Deployment with a Plug‑and‑Play FTP CLI Tool
This article explains how to build a three‑package monorepo using Lerna and TypeScript that automates versioned CDN uploads, FTP resource publishing, and HTML link replacement for legacy front‑end projects, providing step‑by‑step guidance and reusable CLI commands.
Introduction
Legacy projects often still use a non‑separated front‑end architecture where static assets (CSS, JS, images) must be uploaded to a CDN and HTML files updated with the new resource URLs. Manual FTP uploads and version management are error‑prone, especially when multiple features are being tested and released simultaneously.
To eliminate human mistakes, a plug‑and‑play tool is required that can, in three steps, publish a specified local folder from the project root.
Install the tool package.
Add an FTP configuration file.
Add npm scripts for publishing.
Requirement Analysis
After front‑end development, a typical page folder looks like:
/page-1
/css
/js
/img
page-1.htmlThe desired CDN layout is versioned:
/page-1
/v1.0.0
/css
/js
/img
/v1.0.1
...Test and production environments have different upload strategies.
CDN Test Environment
The tool reads existing version directories, determines the highest version, and increments the version (patch, minor, or major) based on user choice.
CDN Production Environment
Resources are copied from the test environment to production using the selected version. Publishing fails if the test version does not exist or the production version already exists, ensuring only incremental updates.
Business‑Specific HTML Intranet Test Environment
After CDN links are replaced, the HTML pages are uploaded to a business‑specific FTP server for backend consumption.
Architecture Design
The solution consists of three npm packages managed in a Lerna monorepo:
@auto/ftp-tools-common : Handles static resource CDN upload and version management.
@auto/ftp-tools-2sc : Extends the common package with HTML upload and business‑specific requirements.
@auto/ftp-tools-utils : Shared utilities (logging, directory handling, FTP helpers) used by both packages.
The monorepo structure:
/FTP-TOOLS
/.vscode
/node_modules
/packages
/ftp-tools-2sc
/ftp-tools-common
/ftp-tools-utils
.eslintrc.js
.prettierrc
lerna.json
package.jsonLerna’s independent mode allows each package to be versioned and published separately. Running lerna publish updates only the changed packages and maintains correct dependency versions.
Project Setup
The toolchain uses Rollup for bundling and TypeScript as the development language.
Util Package and FTP Interface
The ftp-tools-utils package contains three source files:
/src
ftp.ts
index.ts
util.ts util.tsexports logging classes, directory utilities, and Git branch helpers. ftp.ts exports all FTP‑related functions.
Key npm Dependencies
basic-ftp : Provides FTP connection, directory listing, and file transfer.
semver : Implements semantic version handling for validation and incrementing.
compare-versions : Sorts semver‑compatible version strings.
Example: Determining the New Version
/**
* Get the new version for the current release
* @param {NewVersionType} params
*/
export async function getNewVersion({ client, dir, release = 'patch' }: NewVersionType) {
let newVersion = 'v1.0.0';
await client.ensureDir(dir);
const list = await client.list();
const newList = list
.filter(({ type, name }) => type === 2 && semver.valid(name))
.map(({ name }) => name);
if (newList.length > 0) {
const greatestVersion = newList.sort(compareVersions)[newList.length - 1];
newVersion = 'v' + semver.inc(greatestVersion, release);
}
return newVersion;
}All utilities are re‑exported from src/index.ts:
export * as utils from './util';
export * as ftp from './ftp';Common FTP Tool Package
This package implements the CLI logic for both test and production environments.
Test Environment Publishing Flow
User interaction via inquirer to select the folder and version bump type.
FTP operations to fetch the latest version, create the version directory, and upload resources.
Replace local HTML links with CDN URLs that include the new version.
Sample interaction code:
const { folder, release } = await inquirer.prompt([
{ type: 'input', name: 'folder', message: 'Enter the local folder path' },
{ type: 'list', name: 'release', message: 'Select version part to bump', choices: [
{ name: 'Patch', value: 'patch' },
{ name: 'Minor', value: 'minor' },
{ name: 'Major', value: 'major' }
]}
]);Uploading resources to the versioned directory:
export async function uploadResource({ client, newVersion, remoteDir, localDir }: uploadResourceType) {
const remoteDirWithVersion = path.join(remoteDir, newVersion);
const localSubDirArr = getDirectories(localDir);
for (let i = 0; i < localSubDirArr.length; i++) {
const localSubDir = localSubDirArr[i];
const folderName = getFolderName(localSubDir);
await client.ensureDir(path.join(remoteDirWithVersion, folderName));
await client.uploadFromDir(localSubDir);
}
}Replacing HTML resource links with the new CDN path:
const publicPath = ftp_test.publicPath + path.join(folderName, newVersion, '/');
glob.sync(`${folderPath}/*.html`).forEach(async (filepath) => {
const htmlContent = fs.readFileSync(filepath, 'utf8');
const cssReg = /\.\/css\/[^.]*\.css/g;
const jsReg = /\.\/js\/[^.]*\.js/g;
const imgReg = /\.\/img\/[^.]*\.(png|jpg|jpeg|gif)/g;
const newHtmlContent = htmlContent
.replace(cssReg, item => item.replace('./', publicPath))
.replace(jsReg, item => item.replace('./', publicPath))
.replace(imgReg, item => item.replace('./', publicPath));
fs.writeFileSync(filepath, newHtmlContent, 'utf8');
});Production Environment Publishing Flow
The logic mirrors the test flow, but before publishing it verifies that the selected version exists in the test environment and does not already exist in production, preventing overwrites.
CLI Entry Configuration
The package.json defines the executable:
{
"main": "dist/index.js",
"bin": { "ftp-tools-common": "bin/index.js" },
"files": ["bin/", "dist/"]
}Executable stub ( bin/index.js):
#!/usr/bin/env node
require('../dist/cli.js');CLI implementation ( src/cli.ts) dispatches based on the argument:
import mainTest from './publishToTest';
import mainOnline from './publishToOnLine';
const param = process.argv[2];
if (param === '--test') {
mainTest();
} else if (param === '--online') {
mainOnline();
} else {
throw new Error('Parameters are required');
}After installing @auto/ftp-tools-common, a front‑end project adds npm scripts:
"scripts": {
"publish:test": "ftp-tools-common --test",
"publish:online": "ftp-tools-common --online"
}Business‑Specific Tool Package
The @auto/ftp-tools-2sc package adds features such as uploading HTML to a dedicated web test server, performing OA identity verification, and retrieving the current Git branch to handle branch‑specific testing. These extensions are tightly coupled to the business line and are not detailed here.
Conclusion
The described monorepo provides a reusable, version‑aware FTP deployment solution that automates static asset publishing for legacy front‑end projects. Both the common and business‑specific packages are already in production and can be further extended with additional custom tools as needed.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
