跳至主要内容

[Pkg] Env Aligner / npm 套件開發

先簡介一下這個套件好了,Env Aligner 主要是用來檢查 .env.env.example 這兩個檔案的環境變數是否有對齊,這樣可以避免在開發或是部署時因為環墋變數對不齊而造成問題。

做這個套件的起因,就像某業界前輩說的「軟體的開發都源自生活中的問題」,Env Aligner 的出現其實是來自公司需求一個工具在前端自己開發、build 或是 CI/CD pipeline 時可以檢查環境變數是否有齊全,沒有齊全的話就噴個錯誤並把流程終止,來做到環境變數的簡易把控。
在我 survey 的過程裡,其實有發現不少套件有類似的功能,但總在一些細枝末節上跟我的需求稍有差異,沒有辦法達到 100% 吻合,這其實也正常,一般開源性套件通常都是開發者為了因應自身某些需求而開發,然後想說做都做了就放到 npm 上讓大家用用看,而我的需求可能跟他的需求不一樣也是合情合理。
那為了達到 100% 的需求,我一開始寫了個腳本,確確實實地 work 了,但如果要應用在各專案上,不太可能一直 copy / paste 這個腳本,那是否應該做一個新的套件呢?於是 Env Aligner 因應而出。

這篇會先簡介一下 Env Aligner 的功能,但其實重點是後方章節怎麼簡易地製作套件並發布到 npm 上。

Env Aligner 功能介紹

Features

Env Aligner 主要有五個功能:

  1. 參照 .env.example 檢查 .env 檔案中是否有缺少的變數。
  2. 參照 .env.example 檢查 .env 檔案中是否有空值的變數。
  3. 參照 .env.example 檢查 .env 檔案中是否有多餘的變數。(strict 模式限定)
  4. 參照 .env.example 自動對齊 .env 檔案中的變數。此項會移除使用者在 .env 檔案中多出的註解以及變數,但會保留使用者已經填入變數的值。(strict 模式限定)
  5. 參照 .env.example 自動產生一份 .env 檔案,就不用手動 copy / paste。

Installation

npm install env-aligner

CLI Options

OptionDescriptionDefault
--dirRoot directory to scan.process.cwd()
--schemaSchema file name (usually .env.example)..env.example
--envEnv file name to validate or align..env
--strictEnable strict mode: warns about extra keys in .env not in the schema.Not enabled (unless flag is present)
--alignEnable align mode: auto-fix .env format based on schema. ⚠️ Only works with --strict.Not enabled (unless flag is present)
--cloneClone schema to env file if env does not exist in the folder.Not enabled (unless flag is present)

Examples:

npx env-aligner
npx env-aligner --dir .devcontainer --schema config.example --env config.env
npx env-aligner --clone
npx env-aligner --strict --align

Programmatic Usage (Optional)

雖然 Env Aligner 本意上是做成主要使用 CLI,但其實還是有支援再 JavaScript 中使用。當然,我預想會這樣用的人不多。

注意

因為 Env Aligner 開發之初希望它能支援大部分專案,不論新舊,所以它是用 CommonJS 開發的,這樣可以確保在大部分 Node.js 環境中都能運作。
如果專案上是使用 ESM (現今大部分專案都是),那建議在使用時是放在 cjs 腳本中。
但我一概推薦使用 CLI。

check-env.cjs
const envAligner = require('env-aligner')

envAligner({
rootDir: string, // Root directory to scan (default: process.cwd())
fileNames: {
schemaName: string, // Schema file name (e.g., '.env.example')
envName: string // Env file name to validate (e.g., '.env')
},
mode: {
isStrict: boolean, // Enable strict mode (extra key detection)
isAlign: boolean // Enable align mode (auto format fix)
},
isClone: boolean // Whether to generate the env file if missing
})

發布套件

其實發布套件比想像中更簡單,我是看著 PJ 老師的部落格一步一步發布的,氮這裡沒做到自動化啦,因為這玩意兒應該不會有很多更新~
然後在開始前,可以先去 npm 註冊一個帳號,然後在終端機中登入:

npm login

Step 1: 當然是寫好程式碼啦

這沒什麼好說的,就是把你的程式碼寫好,然後確保它可以正常運作。
但這裡我踩到一個坑,就是 CommonJS 跟 ES module 系統的問題。雖說 esm 是現在的趨勢,但不得不說 cjs 還是很多系統的標準,比如 node.js。
我一開始用 esm 開發,然後後來遇到 node.js 無法跑 esm 的問題,所以又花了一晚上把程式碼改回 cjs,所以開發時要想好套件目標是要在哪些系統上運作。

資訊

cjs 跟 esm 最大的差異在 cjs 使用 requiremodule.exports,而 esm 使用 importexport
這式開發時最容易碰到的問題。

Step 2: 準備好 package.json

{
"name": "env-aligner", // 這裡是套件名稱
"version": "1.0.0-beta.2", // 這裡是套件版本號
"description": "A developer-friendly tool.", // 這裡可以寫套件的描述
"keywords": [ // 這裡可以指定關鍵字
".env",
".env.example",
"dotenv",
"environment variables",
"check",
"align"
],
"homepage": "https://github.com/ChungYingHo/env-aligner", // 這裡可以指定套件的首頁 (如果你有單獨做介紹的網頁不然就放 github 也行)
"bugs": { // 這裡可以指定 bug 回報的位置
"url": "https://github.com/ChungYingHo/env-aligner/issues"
},
"repository": { // 這裡可以指定套件的 git 位置
"type": "git",
"url": "https://github.com/ChungYingHo/env-aligner.git"
},
"contributors": [ // 這裡可以列出貢獻者
"Jeremy Ho <ag.cyho@gmail.com>(https://codefictionist.com/)",
"MJC <*****@gmail.com>(https://github.com/*****)"
],
"license": "MIT", // 這裡可以指定授權方式
"main": "dist/index.min.js", // 這裡是指定主要的程式碼路徑
"bin": {
"env-aligner": "dist/cli.min.js" // 這裡是指定 CLI 指令的路徑
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "rollup -c -w",
"build": "rmdir /s /q dist && rollup -c"
},
"dependencies": {
// 這個不用解釋吧
},
"devDependencies": {
// 這個也不用解釋吧
},
"files": [
"dist" // 這裡可以指定要發布的檔案路徑
],
"engines": {
"node": ">=18.0.0" // 這裡可以指定要安裝這個套件需滿足的 node.js 版本
}
}

Step 3: 套件打包 (Optional)

其實如果像是我只輸出一個 js 檔案的話,這步驟可以省略,但如果套件很大的話,通常還是會打包一下,這樣可以減少檔案大小。
套件打包通常用 rollup,我去了解了一下後,會較少用 webpack 是因為 rollup 較適合打包 library。

  1. 安裝 rollup
npm install rollup -D
  1. 安裝一些 rollup 的 plugin

    • rollup-plugin-terser:壓縮 js 檔案
    • @rollup/plugin-json:讀取 json 檔案
    • @rollup/plugin-commonjs:讀取 commonjs 檔案
  2. 建立一個 rollup.config.js 檔案

const { terser } = require('rollup-plugin-terser')
const commonjs = require('@rollup/plugin-commonjs')
const json = require('@rollup/plugin-json')

module.exports = [
{
input: 'src/lib/index.js', // 引用的檔案
output: {
file: 'dist/index.min.js', // 輸出的檔案路徑與名稱
format: 'cjs', // 輸出的格式
sourcemap: true, // 是否產生 sourcemap
exports: 'default' // 輸出的方式,這個跟 cjs 以及 esm 有關
},
plugins: [ // 使用的 plugin
terser(),
commonjs()
],
external: ['dotenv', 'chalk', 'fs', 'path'] // 這裡是指定外部套件,這樣 rollup 就不會把這些套件打包進去
},
{ // 以下同理,這只是打包 CLI 指令的部分
input: 'src/bin/cli.js',
output: {
file: 'dist/cli.min.js',
format: 'cjs',
sourcemap: true,
exports: 'default',
banner: '#!/usr/bin/env node'
},
plugins: [
terser(),
commonjs(),
json()
],
external: ['dotenv', 'chalk', 'fs', 'path', 'commander']
}
]
  1. 最後執行打包指令:
npm run build
資訊

其實 rollup 對 esm 稍微友善一些,但是對 cjs 也是可以的,只是要注意一些額外設定,如 exports
但其實如果真的有錯誤之類的,rollup 還是很貼心地會提示你該怎麼改的。

Step 4: 發布套件

發布套件有分 beta 或正式版,指令也會略有不同:

  1. 發布 beta 版
npm publish --tag beta
  1. 發布正式版
npm publish
注意

假設已經發布了兩個 beta 版,這時會發現怎麼第一個 beta 版標籤變 latest 了?
這是因為 npm 規定有複數版本時,至少要有一個 latest 版本。

移除 npm 上已發布的套件版本

這用在有時為了測試套件穩定性會優先發布 beta 版本,但為了 debug,beta 版可能會源源不絕地增生,這時就需要適時移除 npm 上那些已發布的版本。

npm unpublish env-aligner@1.0.0-beta.1 --force

幫自己的 commit 上 tags

這項不是必做,但如果套件是打算持續更新的,那難免會需要寫 Changelog。
一般寫 Changelog 都會放個 github 上兩個版本之間的 diff,那麼就需要在 commit 上加上 tags,這樣才能在 github 上看到兩個版本之間的差異。

git tag -a v1.0.0 <commit-hash> -m "Release v1.0.0"
git push origin v1.0.0

如果需要 tag 的就是剛剛最新的 commit ,可以直接寫:

git tag -a v1.0.0 -m "Release v1.0.0"

之後有 v2.0.0 時,同樣上完 tag 就可以依下面方式看版本間的 diff:

https://github.com/你的帳號/你的專案/compare/v1.0.0...v2.0.0

Reference

  1. [Guide] 發佈 npm 套件 - 從手動到自動(0):專案與套件建置篇