Frontend Engineering: Building a Universal Scaffolding CLI for Enterprise Projects
This article explains how a custom frontend scaffolding CLI can automate project initialization, template management, and common development tasks across multiple business units, improving efficiency, standardization, and data‑driven insights for large‑scale web applications.
Frontend Engineering – Building an Enterprise‑Wide Scaffolding CLI
Table of Contents
Preface
What is a Scaffolding Tool?
Using the Scaffolding CLI
@focus/cli Architecture
General Capabilities
Conclusion
Preface
As frontend engineering becomes more ingrained in developers' daily work, the need for standardized tooling—such as project scaffolding—grows to automate repetitive tasks like tech stack selection, code conventions, and build/release processes, thereby significantly improving developer productivity.
What is a Scaffolding Tool?
Traditionally, developers must manually perform several steps before writing business code, including technology selection, project initialization, dependency installation, configuration, local server setup, and finally coding.
With the rise of frameworks like Vue and React, official scaffolds (e.g., vue-cli , create‑react‑app ) allow rapid project generation via interactive prompts, letting developers focus on business logic rather than build configuration.
Scaffolding Capabilities
Existing scaffolds target specific frameworks, but in large enterprises different business units may use varied stacks (PC, WAP, mini‑programs, etc.). A unified CLI should generate projects for any target stack through a few commands and selections.
Typical frontend development scenarios include:
Creating a project with integrated common code (utilities, styles, request libraries, component libraries, monitoring, etc.)
Git operations (repository creation, conflict resolution, tagging, etc.)
CICD (build, upload, domain binding, environment switching, rollback, etc.)
Why Not Use Only Automation Build Tools
Tools like Jenkins, Gitlab CI, or Webhooks run on the server side and cannot cover local developer tasks such as project initialization, local Git commands, or custom configuration. Moreover, building plugins for these tools requires additional learning. A JavaScript‑based CLI can handle these local tasks without extra overhead.
Core Value of Scaffolding
The primary goals are to automate repetitive operations, standardize project creation, and provide data‑driven metrics on scaffolding usage.
Automation : eliminate manual copy‑paste and automate Git operations.
Standardization : generate projects from templates and embed CICD capabilities.
Data‑driven : collect usage metrics to quantify time savings.
Using the Scaffolding CLI
Run focus create projectName in the terminal. The command structure is:
focus – main command
create – sub‑command
projectName – parameter
The CLI then guides the user through selections and inputs to generate the project.
@focus/cli Architecture
The CLI is managed with Lerna, similar to large projects like Babel, Vue‑CLI, and CRA. Lerna reduces repetitive tasks (linking, testing, publishing) and ensures version consistency across packages.
Dependency Overview
chalk – console styling
commander – command‑line interface
fs‑extra – enhanced file operations
inquirer – interactive prompts
ora – spinner animations
axios – Gitlab API requests
download‑git‑repo – fetch templates from GitHub/Gitlab
consolidate (ejs) – template rendering
ncp – recursive copy
metalsmith – static site generation with ejs support
semver – version handling
ini – configuration parsing
jscodeshift – AST‑to‑AST transformations (e.g., route insertion)
Core Process of focus create projectName
Execute the command; display logo using figlet .
Use semver to check for newer versions and prompt updates.
Fetch all template projects via Gitlab API (axios).
List available tags for the chosen template.
Select a tag and install dependencies with npm or yarn.
Download the selected tag using download‑git‑repo and cache it in .focusTemplate .
If the template lacks ask‑for‑cli.js , copy files directly with ncp ; otherwise, render prompts with inquirer and process templates via metalsmith .
Install dependencies and run git init to initialize the repository.
Finish.
Core Code Implementation (Step 6)
// copy operation
if (!fs.existsSync(path.join(result, CONFIG.ASK_FOR_CLI as string))) {
await ncp(result, path.resolve(projectName));
successTip();
} else {
const args = require(path.join(result, CONFIG.ASK_FOR_CLI as string));
await new Promise
((resolve, reject) => {
MetalSmith(__dirname)
.source(result)
.destination(path.resolve(projectName))
.use(async (files, metal, done) => {
const obj = await Inquirer.prompt(args.requiredPrompts || args);
const meta = metal.metadata();
Object.assign(meta, obj);
delete files[CONFIG.ASK_FOR_CLI];
done(null, files, metal);
})
.use((files, metal, done) => {
const obj = metal.metadata();
const effectFiles = args.effectFiles || [];
Reflect.ownKeys(files).forEach(async (file) => {
if (effectFiles.length === 0 || effectFiles.includes(file)) {
let content = files[file as string].contents.toString();
if (/<%=(.+?)%>/g.test(content)) {
content = await ejs.render(content, obj);
files[file as string].contents = Buffer.from(content);
}
}
});
successTip();
done(null, files, metal);
})
.build((err) => {
if (err) reject(); else resolve();
});
});
}Core Process of focus add material
Adding a new page typically involves:
Create a directory under src/pages/ with NewPage and files index.tsx , index.less , index.d.ts .
Add a model file src/models/NewPage.ts for state management.
Add a service file src/servers/NewPage.ts for API calls.
Insert a route entry for NewPage into config/routes.ts .
The CLI automates these steps with a single command.
Core Code Implementation (Template Generation)
export const jsContent = `
import React from 'react';
import './index.less';
interface IProps {}
const Page: React.FC
= (props) => {
console.log(props);
return
Page
;
};
`;
export const cssContent = `
// TODO: write here ...
`;
export const modelsContent = (upperPageName, lowerPageName) => (`
import type { Effect, Reducer } from 'umi';
import { get${upperPageName}List } from '@/services/${lowerPageName}';
export type ${upperPageName}ModelState = {
${lowerPageName}List: { list: any[] };
};
export type ${upperPageName}ModelType = {
namespace: string;
state: ${upperPageName}ModelState;
effects: { get${upperPageName}List: Effect };
reducers: { updateState: Reducer };
};
const ${upperPageName}Model: ${upperPageName}ModelType = {
namespace: '${lowerPageName}',
state: { ${lowerPageName}List: { list: [] } },
effects: {
*get${upperPageName}List({ payload }, { call, put }) {
const res = yield call(get${upperPageName}List, payload);
yield put({
type: 'updateState',
payload: {
${lowerPageName}List: { list: res ? res.map(l => ({ ...l, id: l.${lowerPageName}Id, key: l.${lowerPageName}Id })) : [] }
}
});
}
},
reducers: {
updateState(state, action) { return { ...state, ...action.payload }; }
}
};
export default ${upperPageName}Model;
`);
export const servicesContent = (upperPageName, lowerPageName) => (`
import { MainDomain } from '@/utils/env';
import request from './decorator';
export async function get${upperPageName}List(params: any): Promise
{
return request(`${MainDomain}/${lowerPageName}`, { params });
}
`);The mapping between generated files and their destinations is defined in src/add/umi.page/index.ts and applied with jscodeshift to insert route entries.
File Writing Logic
const writeFileTree = async (dir: string, files: any) => {
Object.keys(files).forEach((name) => {
const filePath = path.join(dir, name);
fs.ensureDirSync(path.dirname(filePath));
fs.writeFileSync(filePath, files[name]);
console.log(`${chalk.green(name)} write done.`);
});
};
export default writeFileTree;Summary
The CLI enables one‑click creation of pages, components, and other material by extracting repetitive multi‑file operations into reusable templates, dramatically speeding up delivery and allowing team members to contribute to the scaffolding itself.
Conclusion
The code resides in the repository @careteen/cli .
Scaffolding is essential for improving the efficiency of the entire frontend development workflow. While implementations differ across companies, the core principles—automation, standardization, and data‑driven metrics—remain universal.
Align functionality closely with business needs.
Ensure high extensibility and support for diverse execution environments.
References: https://zhuanlan.zhihu.com/p/376867546 https://github.com/careteenL/cli
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.