In the last article, we can create a new project by using Express Typescript Boilerplate template, it saves us pretty much time to do it from scratch. However, it is still not the best, we can optimize it using NPM and just 1 command line, we get everything.
Setup account
- we will need an account in npm
- Login our account on our pc
- After that, we update our profile by access profile > edit profile. There are 2 things we need to pay attention GitHub Username and Email, they will cause some problems when publishing a package. For example, after running command
npm publish
, it returns403 Forbidden - You do not have permission to publish. Are you logged in as the correct user?
. In case that we can fix it by changing email to whatever, then revert it back to our main email.
Setup project
If we config npm account successfully, just running npm publish
, then we will see the log below
Then it will be shown on our npm packages, and the other developer can reach to the package also
In order to make our package more reliable, we should enable security
. If there is any issue Github will show us as below.
Config command
In this post, we will use our last template as source code for the npm package. And below is my package.json
.
{
"name": "typescript-maker",
"version": "1.1.1",
"description": "Minimalistic boilerplate to quick-start Node.js development in TypeScript.",
"engines": {
"node": ">=14 <15"
},
"bin": {
"typescript-maker": "./bin/generateApp.js"
},
"scripts": {
"start": "node src/index",
"dev": "nodemon --config restart.json",
"clean": "rm -rf coverage build tmp",
"prebuild": "npm run lint",
"build": "tsc -p tsconfig.build.json",
"build:watch": "tsc -w -p tsconfig.build.json",
"lint": "eslint . --ext .ts,.tsx",
"test": "jest"
},
"author": "Dantis Mai",
"license": "Apache-2.0",
"dependencies": {
"commander": "^8.3.0",
"express": "^4.17.1",
"module-alias": "^2.2.2",
"tslib": "~2.3.0",
"winston": "^3.3.3"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
"@types/express": "^4.17.13",
"@types/jest": "^26.0.24",
"@types/node": "~14.14.45",
"@typescript-eslint/eslint-plugin": "~4.28.2",
"@typescript-eslint/parser": "~4.28.2",
"dotenv": "^10.0.0",
"eslint": "~7.30.0",
"eslint-config-prettier": "~8.3.0",
"eslint-plugin-jest": "~24.3.6",
"jest": "^27.0.6",
"jest-html-reporter": "^3.4.1",
"nodemon": "^2.0.12",
"prettier": "~2.3.2",
"rimraf": "^3.0.2",
"supertest": "^6.1.5",
"ts-jest": "^27.0.3",
"ts-node": "^10.2.0",
"ts-node-dev": "^1.1.8",
"tsconfig-paths": "^3.10.1",
"tsutils": "~3.21.0",
"typescript": "~4.3.5"
}
}
In the package.json
file, there are 3 fields, we need to update:
-
name: npm package name. This name will be our npm package name, ignoring the GitHub repository name. For example, my repository name is
express-typescript-boilerplate
, while the package name istypescript-maker
. - version: npm package version. By versioning, we can update the new features with backward compatibility.
-
bin: command configuration. We will direct the source code for the command. As you can see the
bin
field in mypackage.json
, I define"typescript-maker": "./bin/generateApp.js"
, it means thattypescript-maker
is the command name, and the options and arguments are described in./bin/generateApp.js
.
Now, let jump to config our command.
For a sample command, there are 4 steps:
- Verify arguments: verify the number of arguments to make sure, we have enough information.
- Parse arguments and options: get argument value from input
- Validate existing folder: prevent issues when creating a new folder or file. It will work like we clone a repository 2 times at the same directory.
- Define workflow: define what thing we gonna do when we call the command.
- Clear unused files: keep the result clean to not distract the user after running the command.
- Trigger workflow.
Combine everything and we have a sample config for typescript-maker
below
# Verify arguments
if (process.argv.length < 3) {
console.log('You have to provide a name to your app.');
console.log('For example :');
console.log(' typescript-maker my-app');
process.exit(1);
}
# Parse arguments and option
const projectName = process.argv[2];
const currentPath = process.cwd();
const projectPath = path.join(currentPath, projectName);
const git_repo = 'https://github.com/Maithanhdanh/express-typescript-boilerplate.git';
# Validate existing folder
try {
fs.mkdirSync(appPath);
} catch (err) {
if (err.code === 'EEXIST') {
console.log('Directory already exists. Please choose another name for the project.');
} else {
console.log(error);
}
process.exit(1);
}
# define steps in workflow
async function main() {
try {
console.log('Downloading files...');
execSync(`git clone --depth 1 ${git_repo} ${projectPath}`);
// Change directory
process.chdir(appPath);
// Install dependencies
const useYarn = await hasYarn();
console.log('Installing dependencies...');
if (useYarn) {
await runCmd('yarn install');
} else {
await runCmd('npm install');
}
console.log('Dependencies installed successfully.');
console.log();
# Clean unused files
console.log('Removing useless files');
execSync('npx rimraf ./.git');
fs.rmdirSync(path.join(projectPath, 'bin'), { recursive: true});
console.log('The installation is done, this is ready to use !');
} catch (error) {
console.log(error);
}
}
# trigger workflow
main();
In case we want a more complicated command, we can use module commander, which supports us pretty much thing when we design the architecture of the command. After using the commander, I structure my command like this.
This is mine, you can enjoy it.
I am really happy to receive your feedback on this article. Thanks for your precious time reading this.
Top comments (3)
Also: path is not defined (I use import path from 'node:path';) and 'error' is not defined (in error handling, you're using both 'err' and 'error' - they should be the same).
This post clears up quite a bit that I didn't understand about creating an app template. For example, I thought using 'npm create ???' would automatically clone the repo. However, I see several problems in your code. It looks like JavaScript, but uses '#' style comments. Also, you're creating the director appPath, but there's no such variable. I think you meant projectPath???
Also, fs is not defined. Use import * as fs from 'node:fs';