為了避免開發人員開發完畢之後未正確簽入版控,因此實作一個檢查機制,當版控的檔案與產生出來的檔案不一致的時候,透過通知的機制告知開發人員
透過計算檔案的 checksum,比對兩個檔案的 checksum 即可得知是否相同,為了達到這個目的,需要做到下列事項
- 從 Git 取得程式原始碼
- 將前端編譯出來的程式複製到暫存的目錄
- 重新編譯前端程式,輸出至原來的路徑
- 比對暫存目錄、輸出目錄的檔案是否一致
- 若比對結果不一致,則發出通知
複製檔案
利用 node.js 的 fs-extra
套件來複製檔案,好處是透過 CLI 執行該程式,不管在專案的根目錄或是網站的目錄,都可以正確執行複製目錄的行為,所以 jenkins 的 cli 指令也不需要固定寫死
// include fs-extra package
var fs = require("fs-extra");
const path = require("path");
const sourceDir = path.join(__dirname, "../Resource/Source"); // 版控目錄
const generateDir = path.join(__dirname, "../Resource/Bundle"); // 產出目錄
// copy source folder to destination
fs.copy(generateDir, sourceDir, function(err) {
if (err) {
console.log("An error occured while copying the folder.");
return console.error(err);
}
console.log("Copy completed!");
});
重新編譯前端程式
透過已設定好的指令執行即可
yarn build
比對目錄檔案 checksum 是否一致
透過 fs-magic
這個 node.js 的外掛來處理檔案 hash,並比對是否一致,依據最終的結果,透過回傳 EXIT Code 來告知 jenkins 任務的執行是否成功
//compare.js
const _fs = require("fs-magic");
// compare directoy contents based on sha256 hash tables
async function compareDirectories(sourceDir, generateDir) {
let result = true;
let errMsg = [];
// fetch file lists
const [sourceFiles, sourceDirs] = await _fs.scandir(sourceDir, true, true);
const [generateFiles, generateDirs] = await _fs.scandir(generateDir, true, true);
// num files, directories equal ?
if (sourceFiles.length !== generateFiles.length) {
errMsg.push(`版控:[${sourceFiles.length}] 產出:[${generateFiles.length}]:目錄內檔案數量不同 `);
result = false;
}
if (sourceDirs.length !== generateDirs.length) {
errMsg.push(`版控:[${sourceDirs.length}] 產出:[${generateDirs.length}]:子目錄數量不同`);
result = false;
}
// generate file checksums
const hashes1 = await Promise.all(sourceFiles.map(f => _fs.sha256file(f)));
const hashes2 = await Promise.all(generateFiles.map(f => _fs.sha256file(f)));
// convert arrays to objects filename=>hash
const lookup = {};
for (let i = 0; i < hashes2.length; i++) {
// normalized filenames
const f2 = generateFiles[i].substr(generateDir.length);
// assign
lookup[f2] = hashes2[i];
}
// compare dir1 to dir2
for (let i = 0; i < hashes1.length; i++) {
// normalized filenames
const f1 = sourceFiles[i].substr(sourceDir.length);
// exists ?
if (!lookup[f1]) {
errMsg.push(`[ERROR] ${generateDir} 目錄內 ${f1} 檔案不存在`);
result = false;
}
// hash valid ?
if (lookup[f1] !== hashes1[i]) {
errMsg.push(`[ERROR] [${f1}] checksum not match!`);
errMsg.push(`[產 出]:[${lookup[f1]}]`);
errMsg.push(`[版 控]:[${hashes1[i]}]`);
result = false;
}
}
return { result, errMsg };
}
module.exports = compareDirectories;
// compareFiles.js
const compareDirectories = require("./compare.js");
const path = require("path");
const sourceDir = path.join(__dirname, "../Resource/Source"); // 版控目錄
const generateDir = path.join(__dirname, "../Resource/Bundle"); // 產出目錄
async function compareFiles() {
let { result, errMsg } = await compareDirectories(sourceDir, generateDir);
console.log(`result:${result}`);
if (result) {
process.exit(0);
} else {
process.exit(1);
}
}
compareFiles();
透過 exit code 回應執行結果成功或失敗,藉此控制 Jenkins Job 任務結果,可再接續其他下游專案運作
Jenkins 設定範例
新增一個 freeStyle 專案,透過 git 下載 source 完畢後,再新增執行 Windows 批次命令
# STEP1
cd MyProject
yarn
# STEP2
node MyProject\test\copyFiles.js
# STEP3
cd MyProject
yarn build
# STEP4
node MyProject\test\compareFiles.js
之所以分開四個步驟,是因為放在同一個 shell script 區塊,執行 yarn 就會卡住後面的指令。