Skip to content

Commit

Permalink
use npm for building
Browse files Browse the repository at this point in the history
  • Loading branch information
lieser committed Jul 17, 2022
1 parent 0832dcc commit 7d46fb2
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 3 deletions.
5 changes: 3 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"tasks": [
{
"label": "Build xpi",
"type": "shell",
"command": "rm ./dkim_verifier@pl.xpi; & \\\"$($Env:ProgramFiles)/7-Zip/7z.exe\\\" a -tzip -mx1 dkim_verifier@pl.xpi '-i!_locales/*/*.json' '-i!content/*.html' '-i!content/*.css' '-i!content/*.js' ./data/ '-i!experiments/*.js' '-i!experiments/*.json' '-i!modules/*.js' '-i!modules/dkim/*.js' ./thirdparty/ ./CHANGELOG.md ./icon.svg ./LICENSE.txt ./manifest.json ./README.md ./THIRDPARTY_LICENSE.txt",
"detail": "node ./scripts/pack.js",
"type": "npm",
"script": "pack",
"problemMatcher": [],
"group": {
"kind": "build",
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
},
"scripts": {
"update-thirdparty": "node ./scripts/update-thirdparty.js",
"test": "node node_modules/mocha/bin/mocha --recursive --reporter dot"
"test": "node node_modules/mocha/bin/mocha --recursive --reporter dot",
"pack": "node ./scripts/pack.js"
},
"type": "module",
"dependencies": {
Expand All @@ -27,12 +28,16 @@
"devDependencies": {
"@types/firefox-webext-browser": "^94.0.1",
"chai": "^4.3.6",
"chalk": "^5.0.1",
"eslint": "^8.19.0",
"eslint-plugin-jsdoc": "^39.3.3",
"eslint-plugin-mocha": "^10.0.5",
"eslint-plugin-mozilla": "^2.12.4",
"globby": "^13.1.2",
"http-server": "^14.1.1",
"jszip": "^3.10.0",
"mocha": "^10.0.0",
"simple-git": "^3.10.0",
"sinon": "^14.0.0",
"webextensions-api-fake": "^1.3.0"
},
Expand Down
5 changes: 5 additions & 0 deletions scripts/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"node": true
}
}
200 changes: 200 additions & 0 deletions scripts/pack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* Pack the extension into a xpi/zip file.
*/

// @ts-check

import JSZip from "jszip";
import chalk from "chalk";
import { dateToString } from "../modules/utils.mjs.js";
import fs from "node:fs/promises";
import { globby } from "globby";
import { simpleGit } from "simple-git";

/**
* Collect all files that should be included in the packed extension.
*
* @returns {Promise<string[]>}
*/
async function collectFiles() {
/** @type {string[]} */
const files = [];

files.push(...await globby("_locales", {
expandDirectories: {
extensions: ["json"],
}
}));
files.push(...await globby("content", {
expandDirectories: {
extensions: ["html", "css", "js"],
}
}));
files.push(...await globby("data", { expandDirectories: true }));
files.push(...await globby("experiments", {
expandDirectories: {
extensions: ["js", "json"],
}
}));
files.push(...await globby("modules", {
expandDirectories: {
extensions: ["js"],
}
}));
files.push(...await globby("thirdparty", { expandDirectories: true }));

files.push("CHANGELOG.md");
files.push("icon.svg");
files.push("LICENSE.txt");
files.push("manifest.json");
files.push("README.md");
files.push("THIRDPARTY_LICENSE.txt");

return files;
}

/**
* Check if the build is dirty.
*
* @param {string[]} files
* @returns {Promise<boolean>}
*/
async function isDirty(files) {
const status = await simpleGit().status(["--ignored"]);
let dirty = false;

const modifiedFiles = status.modified.filter(value => files.includes(value));
if (modifiedFiles.length !== 0) {
dirty = true;
console.warn(chalk.red("Dirty build: some files are modified!"));
console.log("Included modified files:", modifiedFiles);
}

const addedFiles = status.not_added.filter(value => files.includes(value));
if (addedFiles.length !== 0) {
dirty = true;
console.warn(chalk.red("Dirty build: some untracked files are included!"));
console.log("Included untracked files:", addedFiles);
}

const stagedFiles = status.staged.filter(value => files.includes(value));
if (stagedFiles.length !== 0) {
dirty = true;
console.warn(chalk.red("Dirty build: some files are staged!"));
console.log("Included staged files:", stagedFiles);
}

const ignoredFiles = status.ignored?.filter(value => files.includes(value));
if (ignoredFiles && ignoredFiles.length !== 0) {
dirty = true;
console.warn(chalk.red("Dirty build: some ignored files are included!"));
console.log("Included ignored files:", ignoredFiles);
}

return dirty;
}

/**
* Get the archive name and date that should be used.
*
* @param {boolean} dirty
* @returns {Promise<[string, Date]>}
*/
async function createArchiveInfo(dirty) {
if (dirty) {
return ["dkim_verifier@pl.xpi", new Date()];
}

const changelog = await fs.readFile("CHANGELOG.md", { encoding: "utf8" });

if (changelog.includes("\n## Unreleased")) {
const commit = await simpleGit().log({ maxCount: 1 });
const date = new Date(commit.latest?.date ?? new Date());
const ShortHashLength = 7;
const shortHash = commit.latest?.hash.substring(0, ShortHashLength);
return [`dkim_verifier@pl-${dateToString(date)}-${shortHash}.xpi`, date];
}

const res = changelog.match(/## (\S+) \((\S+)\)/);
if (!res) {
throw new Error("Can not parse changelog");
}
const [, version, date] = res;
if (!version || !date) {
throw new Error("Can not parse changelog");
}
const manifest = JSON.parse(await fs.readFile("manifest.json", "utf8"));
if (version !== manifest.version) {
throw new Error("Version in changelog doe not match manifest");
}
return [`dkim_verifier-${version}.xpi`, new Date(date)];

}

/**
* Try to remove/rename old build.
* In Windows Thunderbird keeps a reference to an installed xpi,
* which results in errors if we try to remove it.
*
* @param {string} name
* @returns {Promise<void>}
*/
async function removeOldBuild(name) {
const inUseName = "dkim_verifier_in_use@pl.xpi";

try {
await fs.rm(inUseName, { force: true });
} catch (error) {
// ignore
}

try {
await fs.rm(name, { force: true });
} catch (error) {
// Sometimes if removing does not work, renaming does
fs.rename(name, inUseName);
await fs.rm(name, { force: true });
}
}

/**
* Pack the given files into a ZIP archive.
*
* @param {string[]} files
* @param {string} archiveName
* @param {Date} archiveDate
* @returns {Promise<void>}
*/
async function packFiles(files, archiveName, archiveDate) {
/**
* Add a single file to a ZIP archive.
*
* @param {JSZip} zip
* @param {string} filePath
* @returns {void}
*/
function addFile(zip, filePath) {
zip.file(filePath, fs.readFile(filePath), {
date: archiveDate,
createFolders: false,
});
}

// Sort files to get deterministic output
files.sort();

const zip = new JSZip();
files.forEach(file => addFile(zip, file));

fs.writeFile(archiveName, await zip.generateAsync({
type: "nodebuffer",
compression: "DEFLATE",
}));
}

const packedFiles = await collectFiles();
const dirty = await isDirty(packedFiles);
const [archiveName, archiveDate] = await createArchiveInfo(dirty);
await removeOldBuild(archiveName);
await packFiles(packedFiles, archiveName, archiveDate);
console.info(`Created packed extension '${archiveName}'`);

0 comments on commit 7d46fb2

Please sign in to comment.