🎉 Copy all the melon code over

This commit is contained in:
trickypr 2021-09-20 10:59:07 +10:00
parent f32d867abc
commit 5777e350af
38 changed files with 2727 additions and 0 deletions

161
src/cmds.ts Normal file
View file

@ -0,0 +1,161 @@
import {
bootstrap,
build,
discard,
download,
downloadArtifacts,
execute,
exportFile,
exportPatches,
fixLineEndings,
importPatches,
init,
licenseCheck,
melonPackage,
reset,
run,
setBranch,
status,
test
} from "./commands";
import { Cmd } from "./types";
export const commands: Cmd[] = [
{
cmd: "bootstrap",
description: "Bootstrap Dot Browser.",
controller: bootstrap
},
{
cmd: "build [os]",
aliases: ["b"],
description:
"Build Dot Browser. Specify the OS param for cross-platform builds.",
options: [
{
arg: "--a, --arch <architecture>",
description:
"Specify architecture for build"
}
],
controller: build
},
{
cmd: "discard <file>",
description: "Discard a files changes.",
options: [
{
arg: "--keep, --keep-patch",
description:
"Keep the patch file instead of removing it"
}
],
controller: discard
},
{
cmd: "download [ffVersion]",
description: "Download Firefox.",
controller: download
},
{
cmd: "download-artifacts",
description:
"Download Windows artifacts from GitHub.",
flags: {
platforms: ["win32"]
},
controller: downloadArtifacts
},
{
cmd: "execute",
description:
"Execute a command inside the engine directory.",
controller: execute
},
{
cmd: "export-file <file>",
description: "Export a changed file as a patch.",
controller: exportFile
},
{
cmd: "export",
aliases: ["export-patches"],
description:
"Export the changed files as patches.",
controller: exportPatches
},
{
cmd: "lfify",
aliases: ["fix-le"],
description:
"Convert CRLF line endings to Unix LF line endings.",
controller: fixLineEndings
},
{
cmd: "import [type]",
aliases: ["import-patches", "i"],
description: "Import patches into the browser.",
options: [
{
arg: "-m, --minimal",
description:
"Import patches in minimal mode"
},
{
arg: "--noignore",
description:
"Bypass .gitignore. You shouldn't really use this."
}
],
controller: importPatches
},
{
cmd: "init <source>",
aliases: ["initialise", "initialize"],
description: "Initialise the Firefox directory.",
controller: init
},
{
cmd: "license-check",
aliases: ["lc"],
description:
"Check the src directory for the absence of MPL-2.0 header.",
controller: licenseCheck
},
{
cmd: "package",
aliases: ["pack"],
description:
"Package the browser for distribution.",
controller: melonPackage
},
{
cmd: "reset",
description:
"Reset the source directory to stock Firefox.",
controller: reset
},
{
cmd: "run [chrome]",
aliases: ["r", "open"],
description: "Run the browser.",
controller: run
},
{
cmd: "set-branch <branch>",
description: "Change the default branch.",
controller: setBranch
},
{
cmd: "status",
description:
"Status and files changed for src directory.",
controller: status
},
{
cmd: "test",
description:
"Run the test suite for Dot Browser.",
controller: test
}
];

89
src/commands/bootstrap.ts Normal file
View file

@ -0,0 +1,89 @@
/// <reference path="./linus.d.ts"/>
import distro from "linus";
import { bin_name } from "..";
import { log } from "../";
import { ENGINE_DIR } from "../constants";
import { dispatch } from "../utils";
import { pacmanInstall } from "./bootstrap/arch";
export const bootstrap = async () => {
if (process.platform == "win32")
log.error(
`You do not need to bootstrap on Windows. As long as you ran |${bin_name} download-artifacts| everything should work fine.`
);
log.info(`Bootstrapping Dot Browser for Desktop...`);
const args = ["--application-choice", "browser"];
if (process.platform === "linux") {
linuxBootstrap();
} else {
console.info(
`Custom bootstrapping doesn't work on ${process.platform}. Consider contributing to improve support`
);
console.info(
`Passing through to |mach bootstrap|`
);
await dispatch(
`./mach`,
["bootstrap", ...args],
ENGINE_DIR
);
}
};
function getDistro(): Promise<string> {
return new Promise((resolve, reject) => {
distro.name((err: Error, name: string) => {
if (name) resolve(name);
else {
reject(
err || "Failed to get linux distro"
);
}
});
});
}
async function linuxBootstrap() {
const distro = await getDistro();
switch (distro) {
// Both arch and manjaro use the same package repo and the same package manager
case "ManjaroLinux":
case "ArchLinux":
console.log(
await pacmanInstall(
// Shared packages
"base-devel",
"nodejs",
"unzip",
"zip",
// Needed for desktop apps
"alsa-lib",
"dbus-glib",
"gtk3",
"libevent",
"libvpx",
"libxt",
"mime-types",
"nasm",
"startup-notification",
"gst-plugins-base-libs",
"libpulse",
"xorg-server-xvfb",
"gst-libav",
"gst-plugins-good"
)
);
break;
default:
log.error(`Unimplemented distro '${distro}'`);
}
}

View file

@ -0,0 +1,14 @@
import execa from "execa";
export async function pacmanInstall(
...packages: string[]
): Promise<string> {
return (
await execa("sudo", [
"pacman",
"--noconfirm",
"-S",
...packages
])
).stdout;
}

187
src/commands/build.ts Normal file
View file

@ -0,0 +1,187 @@
import execa from "execa";
import {
existsSync,
readFileSync,
writeFileSync
} from "fs";
import { join, resolve } from "path";
import { bin_name, log } from "..";
import {
ARCHITECTURE,
BUILD_TARGETS,
CONFIGS_DIR,
ENGINE_DIR
} from "../constants";
import { dispatch } from "../utils";
const platform: any = {
win32: "windows",
darwin: "macos",
linux: "linux"
};
const applyConfig = async (os: string, arch: string) => {
log.info("Applying mozconfig...");
let commonConfig = readFileSync(
resolve(CONFIGS_DIR, "common", "mozconfig"),
"utf-8"
);
const changesetPrefix = commonConfig
.split("\n")
.find((ln) =>
ln.startsWith("export MOZ_SOURCE_CHANGESET=")
);
const changeset = changesetPrefix?.replace(
/export MOZ_SOURCE_CHANGESET=/,
""
);
const { stdout: gitSha } = await execa("git", [
"rev-parse",
"HEAD"
]);
console.log(changeset, gitSha);
if (changeset)
commonConfig = commonConfig.replace(
changeset,
gitSha
);
writeFileSync(
resolve(CONFIGS_DIR, "common", "mozconfig"),
commonConfig
);
const osConfig = readFileSync(
resolve(
CONFIGS_DIR,
os,
arch === "i686"
? "mozconfig-i686"
: "mozconfig"
),
"utf-8"
);
// Allow a custom config to be placed in /mozconfig. This will not be committed
// to origin
const customConfig = existsSync(
join(process.cwd(), "mozconfig")
)
? readFileSync(
join(process.cwd(), "mozconfig")
).toString()
: "";
const mergedConfig = `# This file is automatically generated. You should only modify this if you know what you are doing!\n\n${commonConfig}\n\n${osConfig}\n\n${customConfig}`;
writeFileSync(
resolve(ENGINE_DIR, "mozconfig"),
mergedConfig
);
log.info(`Config for this \`${os}\` build:`);
mergedConfig.split("\n").map((ln) => {
if (
ln.startsWith("mk") ||
ln.startsWith("ac") ||
ln.startsWith("export")
)
log.info(
`\t${ln
.replace(/mk_add_options /, "")
.replace(/ac_add_options /, "")
.replace(/export /, "")}`
);
});
};
const genericBuild = async (os: string, tier: string) => {
log.info(`Building for "${os}"...`);
log.warning(
`If you get any dependency errors, try running |${bin_name} bootstrap|.`
);
await dispatch(
`./mach`,
["build"].concat(tier ? [tier] : []),
ENGINE_DIR
);
};
const parseDate = (d: number) => {
d = d / 1000;
var h = Math.floor(d / 3600);
var m = Math.floor((d % 3600) / 60);
var s = Math.floor((d % 3600) % 60);
var hDisplay =
h > 0
? h + (h == 1 ? " hour, " : " hours, ")
: "";
var mDisplay =
m > 0
? m + (m == 1 ? " minute, " : " minutes, ")
: "";
var sDisplay =
s > 0
? s + (s == 1 ? " second" : " seconds")
: "";
return hDisplay + mDisplay + sDisplay;
};
const success = (date: number) => {
// mach handles the success messages
console.log();
log.info(
`Total build time: ${parseDate(
Date.now() - date
)}.`
);
};
interface Options {
arch: string;
}
export const build = async (
tier: string,
options: Options
) => {
let d = Date.now();
// Host build
const prettyHost = platform[process.platform as any];
if (BUILD_TARGETS.includes(prettyHost)) {
let arch = "64bit";
if (options.arch) {
if (!ARCHITECTURE.includes(options.arch))
return log.error(
`We do not support "${
options.arch
}" build right now.\nWe only currently support ${JSON.stringify(
ARCHITECTURE
)}.`
);
else arch = options.arch;
}
applyConfig(prettyHost, options.arch);
setTimeout(async () => {
await genericBuild(prettyHost, tier).then(
(_) => success(d)
);
}, 2500);
}
};

71
src/commands/discard.ts Normal file
View file

@ -0,0 +1,71 @@
import execa from "execa";
import { existsSync, statSync } from "fs";
import { resolve } from "path";
import rimraf from "rimraf";
import { log } from "..";
import { ENGINE_DIR, PATCHES_DIR } from "../constants";
interface Options {
keep?: boolean;
fromRemote?: string;
}
const remotes = {
ff: (file: string, version: string) =>
`https://hg.mozilla.org/experimental/firefox-unified-stage/raw-file/FIREFOX_${version
.replace(" ", "_")
.replace(".", "_")}_RELEASE/${file}`,
dot: (file: string, ref: string) =>
`https://raw.githubusercontent.com/dothq/browser-desktop/${ref}/${file}`
};
export const discard = async (
file: string,
options: Options
) => {
log.info(`Discarding ${file}...`);
if (!statSync(file).isFile())
throw new Error("Target must be a file.");
// @todo add remote discard
if (options.fromRemote) {
if (
options.fromRemote == "ff" ||
options.fromRemote == "firefox"
) {
} else if (options.fromRemote == "dot") {
} else {
throw new Error(
"Unrecognised remote type. Expected `ff` or `dot`."
);
}
} else {
if (!existsSync(resolve(ENGINE_DIR, file)))
throw new Error(
`File ${file} could not be found in src directory. Check the path for any mistakes and try again.`
);
const patchFile = resolve(
PATCHES_DIR,
file.replace(/\//g, "-").replace(/\./g, "-") +
".patch"
);
if (!existsSync(patchFile))
throw new Error(
`File ${file} does have an associated patch in the patches directory.`
);
const { stdout, exitCode } = await execa(
"git",
["apply", "-R", "-p", "1", patchFile],
{ cwd: ENGINE_DIR }
);
if (exitCode == 0) {
log.success(`Discarded changes to ${file}.`);
if (!options.keep) rimraf.sync(patchFile);
} else throw new Error(stdout);
}
};

View file

@ -0,0 +1,81 @@
import axios from "axios";
import execa from "execa";
import fs from "fs";
import { homedir } from "os";
import { posix, resolve, sep } from "path";
import { log } from "..";
export const downloadArtifacts = async () => {
if (process.platform !== "win32")
return log.error(
"This is not a Windows machine, will not download artifacts."
);
if (process.env.MOZILLABUILD)
return log.error(
"Run this command in Git Bash, it does not work in Mozilla Build."
);
const filename = "mozbuild.tar.bz2";
const url = `https://github.com/dothq/windows-artifacts/releases/latest/download/mozbuild.tar.bz2`;
let home = homedir().split(sep).join(posix.sep);
if (process.platform == "win32") {
home =
"/" +
home
.replace(/\:/, "")
.replace(/\\/g, "/")
.toLowerCase();
}
log.info(`Downloading Windows artifacts...`);
const { data, headers } = await axios.get(url, {
responseType: "stream"
});
const length = headers["content-length"];
const writer = fs.createWriteStream(
resolve(process.cwd(), filename)
);
let receivedBytes = 0;
data.on("data", (chunk: any) => {
receivedBytes += chunk.length;
let rand = Math.floor(Math.random() * 1000 + 1);
if (rand > 999.5) {
let percentCompleted = parseInt(
Math.round(
(receivedBytes * 100) / length
).toFixed(0)
);
if (
percentCompleted % 2 == 0 ||
percentCompleted >= 100
)
return;
log.info(
`\t${filename}\t${percentCompleted}%...`
);
}
});
data.pipe(writer);
data.on("end", async () => {
log.info("Unpacking mozbuild...");
await execa("tar", [
"-xvf",
filename,
"-C",
home
]);
log.info("Done extracting mozbuild artifacts.");
});
};

269
src/commands/download.ts Normal file
View file

@ -0,0 +1,269 @@
import axios from "axios";
import chalk from "chalk";
import execa from "execa";
import fs, {
existsSync,
rmdirSync,
writeFileSync
} from "fs";
import { ensureDirSync, removeSync } from "fs-extra";
import ora from "ora";
import { homedir } from "os";
import { posix, resolve, sep } from "path";
import { bin_name, log } from "..";
import { ENGINE_DIR } from "../constants";
import { getLatestFF, writeMetadata } from "../utils";
import { downloadArtifacts } from "./download-artifacts";
const pjson = require("../../package.json");
let initProgressText = "Initialising...";
let initProgress: any = ora({
text: `Initialising...`,
prefixText: chalk.blueBright.bold("00:00:00"),
spinner: {
frames: [""]
},
indent: 0
});
const onData = (data: any) => {
const d = data.toString();
d.split("\n").forEach((line: any) => {
if (line.trim().length !== 0) {
let t = line.split(" ");
t.shift();
initProgressText = t.join(" ");
}
});
};
const unpack = async (name: string, version: string) => {
let cwd = process.cwd().split(sep).join(posix.sep);
if (process.platform == "win32") {
cwd = "./";
}
initProgress.start();
setInterval(() => {
if (initProgress) {
initProgress.text = initProgressText;
initProgress.prefixText =
chalk.blueBright.bold(log.getDiff());
}
}, 100);
initProgressText = `Unpacking Firefox...`;
try {
rmdirSync(ENGINE_DIR);
} catch (e) {}
ensureDirSync(ENGINE_DIR);
let tarProc = execa("tar", [
"--transform",
"s,firefox-89.0,engine,",
`--show-transformed`,
"-xf",
resolve(cwd, ".dotbuild", "engines", name)
]);
(tarProc.stdout as any).on("data", onData);
(tarProc.stdout as any).on("error", onData);
tarProc.on("exit", () => {
if (process.env.CI_SKIP_INIT)
return log.info("Skipping initialisation.");
const initProc = execa(`./${bin_name}`, [
"init",
"engine"
]);
(initProc.stdout as any).on("data", onData);
(initProc.stdout as any).on("error", onData);
initProc.on("exit", async () => {
initProgressText = "";
initProgress.stop();
initProgress = null;
await new Promise((resolve) =>
setTimeout(resolve, 5000)
);
log.success(
`You should be ready to make changes to Dot Browser.\n\n\t You should import the patches next, run |${bin_name} import|.\n\t To begin building Dot, run |${bin_name} build|.`
);
console.log();
pjson.versions["firefox-display"] = version;
pjson.versions["firefox"] =
version.split("b")[0];
writeFileSync(
resolve(process.cwd(), "package.json"),
JSON.stringify(pjson, null, 4)
);
await writeMetadata();
removeSync(
resolve(cwd, ".dotbuild", "engines", name)
);
process.exit(0);
});
});
};
export const download = async (
firefoxVersion?: string
) => {
if (firefoxVersion)
log.warning(
`A custom Firefox version is being used. Some features of Dot may not work as expected.`
);
if (!firefoxVersion) {
firefoxVersion =
pjson.versions["firefox-display"];
}
let version = await getLatestFF();
if (firefoxVersion) {
version = firefoxVersion;
}
const base = `https://archive.mozilla.org/pub/firefox/releases/${version}/source/`;
const filename = `firefox-${version}.source.tar.xz`;
const url = `${base}${filename}`;
log.info(`Locating Firefox release ${version}...`);
ensureDirSync(
resolve(process.cwd(), `.dotbuild`, `engines`)
);
if (
existsSync(
resolve(
process.cwd(),
`.dotbuild`,
`engines`,
`firefox-${version.split("b")[0]}`
)
)
) {
log.error(
`Cannot download version ${
version.split("b")[0]
} as it already exists at "${resolve(
process.cwd(),
`firefox-${version.split("b")[0]}`
)}"`
);
}
if (version == firefoxVersion)
log.info(
`Version is frozen at ${firefoxVersion}!`
);
if (version.includes("b"))
log.warning(
"Version includes non-numeric characters. This is probably a beta."
);
if (
fs.existsSync(
resolve(
process.cwd(),
`.dotbuild`,
`engines`,
"firefox",
version.split("b")[0]
)
) ||
fs.existsSync(
resolve(
process.cwd(),
"firefox",
"firefox-" + version.split("b")[0]
)
)
)
log.error(
`Workspace with version "${
version.split("b")[0]
}" already exists.\nRemove that workspace and run |${bin_name} download ${version}| again.`
);
log.info(`Downloading Firefox release ${version}...`);
const { data, headers } = await axios.get(url, {
responseType: "stream"
});
const length = headers["content-length"];
const writer = fs.createWriteStream(
resolve(
process.cwd(),
`.dotbuild`,
`engines`,
filename
)
);
let receivedBytes = 0;
data.on("data", (chunk: any) => {
receivedBytes += chunk.length;
let rand = Math.floor(Math.random() * 1000 + 1);
if (rand > 999.5) {
let percentCompleted = parseInt(
Math.round(
(receivedBytes * 100) / length
).toFixed(0)
);
if (
percentCompleted % 2 == 0 ||
percentCompleted >= 100
)
return;
log.info(
`\t${filename}\t${percentCompleted}%...`
);
}
});
data.pipe(writer);
data.on("end", async () => {
await unpack(filename, version);
if (process.platform === "win32") {
if (
existsSync(
resolve(homedir(), ".mozbuild")
)
) {
log.info(
"Mozbuild directory already exists, not redownloading"
);
} else {
log.info(
"Mozbuild not found, downloading artifacts."
);
await downloadArtifacts();
}
}
});
};

26
src/commands/execute.ts Normal file
View file

@ -0,0 +1,26 @@
import { existsSync } from "fs";
import { log } from "..";
import { ENGINE_DIR } from "../constants";
import { dispatch } from "../utils";
export const execute = async (_: any, cmd: any[]) => {
if (existsSync(ENGINE_DIR)) {
if (!cmd || cmd.length == 0)
log.error(
"You need to specify a command to run."
);
const bin = cmd[0];
const args = cmd;
args.shift();
log.info(
`Executing \`${bin}${
args.length !== 0 ? ` ` : ``
}${args.join(" ")}\` in \`src\`...`
);
dispatch(bin, args, ENGINE_DIR, true);
} else {
log.error(`Unable to locate src directory.`);
}
};

View file

@ -0,0 +1,64 @@
import execa from "execa";
import { existsSync, writeFileSync } from "fs";
import { ensureDirSync } from "fs-extra";
import { resolve } from "path";
import { log } from "..";
import { ENGINE_DIR, SRC_DIR } from "../constants";
import { delay } from "../utils";
export const exportFile = async (file: string) => {
log.info(`Exporting ${file}...`);
if (!existsSync(resolve(ENGINE_DIR, file)))
throw new Error(
`File ${file} could not be found in engine directory. Check the path for any mistakes and try again.`
);
const proc = await execa(
"git",
[
"diff",
"--src-prefix=a/",
"--dst-prefix=b/",
"--full-index",
resolve(ENGINE_DIR, file)
],
{
cwd: ENGINE_DIR,
stripFinalNewline: false
}
);
const name =
file
.split("/")
[
file.replace(/\./g, "-").split("/")
.length - 1
].replace(/\./g, "-") + ".patch";
const patchPath = file
.replace(/\./g, "-")
.split("/")
.slice(0, -1);
ensureDirSync(resolve(SRC_DIR, ...patchPath));
if (proc.stdout.length >= 8000) {
log.warning("");
log.warning(
`Exported patch is over 8000 characters. This patch may become hard to manage in the future.`
);
log.warning(
`We recommend trying to decrease your patch size by making minimal edits to the source.`
);
log.warning("");
await delay(2000);
}
writeFileSync(
resolve(SRC_DIR, ...patchPath, name),
proc.stdout
);
log.info(`Wrote "${name}" to patches directory.`);
console.log();
};

View file

@ -0,0 +1,218 @@
import execa from "execa";
import {
appendFileSync,
createWriteStream,
existsSync,
mkdirSync,
rmdirSync,
writeFileSync
} from "fs";
import { copySync, ensureDirSync } from "fs-extra";
import { resolve } from "path";
import { log } from "..";
import {
COMMON_DIR,
ENGINE_DIR,
PATCHES_DIR
} from "../constants";
import manualPatches from "../manual-patches";
const flags: {
[key: string]: string;
} = {
D: "delete",
M: "modify",
A: "add"
};
const getFiles = async (flags: string, cwd: string) => {
let { stdout: ignored } = await execa(
"git",
[
"ls-files",
`-${flags.toLowerCase()}`,
"-i",
"-o",
"--exclude-standard"
],
{ cwd }
);
let { stdout: fls } = await execa(
"git",
[
"diff",
`--diff-filter=${flags}`,
"--name-only",
"--ignore-space-at-eol"
],
{ cwd }
);
const files = fls.split("\n").filter((i: any) => {
return !(
ignored.split("\n").includes(i) ||
i == ".gitignore"
);
}); // this filters out the manual patches
log.info(
`Ignoring ${ignored.split("\n").length} files...`
);
const fileNames: any = files.map((f: any) => {
if (f.length !== 0) {
return (
f
.replace(/\//g, "-")
.replace(/\./g, "-") + ".patch"
);
}
});
return { files, fileNames };
};
const exportModified = async (
patchesDir: string,
cwd: string
) => {
const { files, fileNames } = await getFiles("M", cwd);
var filesWritten = 0;
await Promise.all(
files.map(async (file: any, i: any) => {
if (file) {
try {
const proc = execa(
"git",
[
"diff",
"--src-prefix=a/",
"--dst-prefix=b/",
"--full-index",
file
],
{
cwd,
stripFinalNewline: false
}
);
const name = fileNames[i];
proc.stdout?.pipe(
createWriteStream(
resolve(patchesDir, name)
)
);
appendFileSync(
resolve(PATCHES_DIR, ".index"),
`${name} - ${file}\n`
);
++filesWritten;
} catch (e) {
log.error(e);
return;
}
}
})
);
log.info(
`Wrote ${filesWritten} to patches directory.`
);
};
const exportFlag = async (
flag: string,
cwd: string,
actions: any[]
) => {
const { files } = await getFiles(flag, cwd);
actions.push({
action: flags[flag],
target: files
});
return actions;
};
const exportManual = async (cwd: string) => {
return new Promise(async (resol) => {
manualPatches.forEach((patch) => {
if (patch.action == "copy") {
if (typeof patch.src == "string") {
const inSrc = resolve(cwd, patch.src);
const outsideSrc = resolve(
COMMON_DIR,
patch.src
);
if (!existsSync(inSrc))
return log.error(
`Cannot find "${patch.src}" from manual patches.`
);
if (!existsSync(outsideSrc))
ensureDirSync(outsideSrc); // make sure target dir exists before copying
copySync(inSrc, outsideSrc);
} else if (Array.isArray(patch.src)) {
patch.src.forEach((p) => {
const inSrc = resolve(cwd, p);
const outsideSrc = resolve(
COMMON_DIR,
p
);
if (!existsSync(inSrc))
return log.error(
`Cannot find "${p}" from manual patches.`
);
if (!existsSync(outsideSrc))
ensureDirSync(outsideSrc); // make sure target dir exists before copying
copySync(inSrc, outsideSrc);
});
}
}
});
});
};
export const exportPatches = async () => {
throw new Error(
"export-patches has been deprecated in favour of export-file. This change has been made to limit the amount of active patches we have in the tree."
);
let actions: any[] = [];
log.info(`Wiping patches directory...`);
console.log();
// TODO: Replace this with fs.rmSync(path, { recursive: true }) when node 12 is deprecated
// This function has been depriciated, however its replacement was only available
// from v14.14.0 onwards (https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fs_fs_rmsync_path_options)
rmdirSync(PATCHES_DIR, { recursive: true });
mkdirSync(PATCHES_DIR);
writeFileSync(resolve(PATCHES_DIR, ".index"), "");
log.info("Exporting modified files...");
await exportModified(PATCHES_DIR, ENGINE_DIR);
console.log();
log.info("Exporting deleted files...");
await exportFlag("D", ENGINE_DIR, actions);
console.log();
log.info("Exporting manual patches...");
await exportManual(ENGINE_DIR);
console.log();
copySync(
resolve(ENGINE_DIR, "dot"),
resolve(process.cwd(), "browser")
);
};

49
src/commands/fix-le.ts Normal file
View file

@ -0,0 +1,49 @@
import {
existsSync,
readdirSync,
readFileSync
} from "fs-extra";
import { resolve } from "path";
import { log } from "..";
import { ENGINE_DIR, PATCHES_DIR } from "../constants";
import { dispatch } from "../utils";
export const fixLineEndings = async () => {
let patches = readdirSync(PATCHES_DIR);
patches = patches.filter((p) => p !== ".index");
await Promise.all(
patches.map(async (patch) => {
const patchContents = readFileSync(
resolve(PATCHES_DIR, patch),
"utf-8"
);
const originalPath = patchContents
.split("diff --git a/")[1]
.split(" b/")[0];
if (
existsSync(
resolve(ENGINE_DIR, originalPath)
)
) {
dispatch(
"dos2unix",
[originalPath],
ENGINE_DIR
).then(async (_) => {
await dispatch(
"dos2unix",
[patch],
PATCHES_DIR
);
});
} else {
log.warning(
`Skipping ${patch} as it no longer exists in tree...`
);
}
})
);
};

View file

@ -0,0 +1,141 @@
import { sync } from "glob";
import { bin_name, log } from "..";
import { SRC_DIR } from "../constants";
import Patch from "../controllers/patch";
import manualPatches from "../manual-patches";
import { delay, dispatch } from "../utils";
const {
versions: { dot }
} = require("../../package.json");
const importManual = async (
minimal?: boolean,
noIgnore?: boolean
) => {
log.info(
`Applying ${manualPatches.length} manual patches...`
);
if (!minimal) console.log();
await delay(500);
return new Promise(async (res, rej) => {
var total = 0;
var i = 0;
for await (let {
name,
action,
src,
markers,
indent
} of manualPatches) {
++i;
const p = new Patch({
name,
action,
src,
type: "manual",
status: [i, manualPatches.length],
markers,
indent,
options: {
minimal,
noIgnore
}
});
await delay(100);
await p.apply();
}
log.success(
`Successfully imported ${manualPatches.length} manual patches!`
);
console.log();
await delay(1000);
res(total);
});
};
const importPatchFiles = async (
minimal?: boolean,
noIgnore?: boolean
) => {
let patches = sync("**/*.patch", {
nodir: true,
cwd: SRC_DIR
});
patches = patches
.filter((p) => p !== ".index")
.filter((p) => !p.includes("node_modules"));
log.info(`Applying ${patches.length} patch files...`);
if (!minimal) console.log();
await delay(500);
var i = 0;
for await (const patch of patches) {
++i;
const p = new Patch({
name: patch,
type: "file",
status: [i, patches.length],
options: {
minimal,
noIgnore
}
});
await delay(100);
await p.apply();
}
console.log();
await dispatch(
`./${bin_name}`,
["doctor", "patches"],
process.cwd(),
true,
true
);
log.success(
`Successfully imported ${patches.length} patch files!`
);
};
interface Args {
minimal?: boolean;
noignore?: boolean;
}
export const importPatches = async (
type: string,
args: Args
) => {
if (type) {
if (type == "manual")
await importManual(args.minimal);
else if (type == "file")
await importPatchFiles(args.minimal);
} else {
await importManual(args.minimal, args.noignore);
await importPatchFiles(
args.minimal,
args.noignore
);
}
};

18
src/commands/index.ts Normal file
View file

@ -0,0 +1,18 @@
export * from "./bootstrap";
export * from "./build";
export * from "./discard";
export * from "./download";
export * from "./download-artifacts";
export * from "./execute";
export * from "./export-file";
export * from "./export-patches";
export * from "./fix-le";
export * from "./import-patches";
export * from "./init";
export * from "./license-check";
export * from "./package";
export * from "./reset";
export * from "./run";
export * from "./set-branch";
export * from "./status";
export * from "./test";

68
src/commands/init.ts Normal file
View file

@ -0,0 +1,68 @@
import { Command } from "commander";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { bin_name, log } from "..";
import { dispatch } from "../utils";
export const init = async (directory: Command) => {
if (process.platform == "win32") {
// Because Windows cannot handle paths correctly, we're just calling a script as the workaround.
log.info(
"Successfully downloaded browser source. Please run |./windows-init.sh| to finish up."
);
process.exit(0);
}
const cwd = process.cwd();
const dir = resolve(
cwd as string,
directory.toString()
);
if (!existsSync(dir)) {
log.error(
`Directory "${directory}" not found.\nCheck the directory exists and run |${bin_name} init| again.`
);
}
let version = readFileSync(
resolve(
cwd,
directory.toString(),
"browser",
"config",
"version_display.txt"
),
"utf-8"
);
if (!version)
log.error(
`Directory "${directory}" not found.\nCheck the directory exists and run |${bin_name} init| again.`
);
version = version.trim().replace(/\\n/g, "");
await dispatch("git", ["init"], dir as string);
await dispatch(
"git",
["checkout", "--orphan", version],
dir as string
);
await dispatch(
"git",
["add", "-v", "-f", "."],
dir as string
);
await dispatch(
"git",
["commit", "-am", `"Firefox ${version}"`],
dir as string
);
await dispatch(
"git",
["checkout", "-b", "dot"],
dir as string
);
};

View file

@ -0,0 +1,92 @@
import chalk from "chalk";
import { readdirSync, readFileSync } from "fs-extra";
import { resolve } from "path";
import { log } from "..";
import { ENGINE_DIR, PATCHES_DIR } from "../constants";
const ignoredExt = [".json", ".bundle.js"];
export const licenseCheck = async () => {
log.info("Checking project...");
let patches = readdirSync(PATCHES_DIR).map((p) => p);
patches = patches.filter((p) => p !== ".index");
const originalPaths = patches.map((p) => {
const data = readFileSync(
resolve(PATCHES_DIR, p),
"utf-8"
);
return data
.split("diff --git a/")[1]
.split(" b/")[0];
});
let passed: string[] = [];
let failed: string[] = [];
let ignored: string[] = [];
originalPaths.forEach((p) => {
const data = readFileSync(
resolve(ENGINE_DIR, p),
"utf-8"
);
const headerRegion = data
.split("\n")
.slice(0, 32)
.join(" ");
const passes =
headerRegion.includes(
"http://mozilla.org/MPL/2.0"
) &&
headerRegion.includes(
"This Source Code Form"
) &&
headerRegion.includes("copy of the MPL");
const isIgnored = ignoredExt.find((i) =>
p.endsWith(i)
)
? true
: false;
isIgnored && ignored.push(p);
if (!isIgnored) {
if (passes) passed.push(p);
else if (!passes) failed.push(p);
}
});
let maxPassed = 5;
let i = 0;
for (const p of passed) {
log.info(
`${p}... ${chalk.green("✔ Pass - MPL-2.0")}`
);
if (i >= maxPassed) {
log.info(
`${chalk.gray.italic(
`${
passed.length - maxPassed
} other files...`
)} ${chalk.green("✔ Pass - MPL-2.0")}`
);
break;
}
++i;
}
failed.forEach((p, i) => {
log.info(`${p}... ${chalk.red("❗ Failed")}`);
});
ignored.forEach((p, i) => {
log.info(`${p}... ${chalk.gray(" Ignored")}`);
});
};

5
src/commands/linus.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
declare module "linus" {
export function name(
callback: (error: Error, name: string) => void
): void;
}

35
src/commands/package.ts Normal file
View file

@ -0,0 +1,35 @@
import execa from "execa";
import { existsSync } from "fs";
import { resolve } from "path";
import { bin_name, log } from "..";
import { ENGINE_DIR } from "../constants";
export const melonPackage = async () => {
if (existsSync(ENGINE_DIR)) {
const artifactPath = resolve(ENGINE_DIR, "mach");
if (existsSync(artifactPath)) {
const args = ["package"];
log.info(
`Packaging \`dot\` with args ${JSON.stringify(
args.slice(1, 0)
)}...`
);
execa(artifactPath, args).stdout?.pipe(
process.stdout
);
} else {
log.error(
`Cannot binary with name \`mach\` in ${resolve(
ENGINE_DIR
)}`
);
}
} else {
log.error(
`Unable to locate any source directories.\nRun |${bin_name} download| to generate the source directory.`
);
}
};

171
src/commands/reset.ts Normal file
View file

@ -0,0 +1,171 @@
import execa from "execa";
import { existsSync } from "fs-extra";
import { resolve } from "path";
import { confirm } from "promptly";
import rimraf from "rimraf";
import { bin_name, log } from "..";
import { ENGINE_DIR } from "../constants";
import { IPatch } from "../interfaces/patch";
import manualPatches from "../manual-patches";
export const reset = async () => {
try {
log.warning(
"This will clear all your unexported changes in the `src` directory!"
);
log.warning(
`You can export your changes by running |${bin_name} export|.`
);
confirm(`Are you sure you want to continue?`, {
default: "false"
})
.then(async (answer) => {
if (answer) {
await execa(
"git",
["checkout", "."],
{ cwd: ENGINE_DIR }
);
manualPatches.forEach(
async (patch: IPatch) => {
const { src, action } = patch;
if (action == "copy") {
if (
typeof src == "string"
) {
const path = resolve(
ENGINE_DIR,
src
);
if (
path !==
ENGINE_DIR
) {
log.info(
`Deleting ${src}...`
);
if (
existsSync(
path
)
)
rimraf.sync(
path
);
}
} else if (
Array.isArray(src)
) {
src.forEach((i) => {
const path =
resolve(
ENGINE_DIR,
i
);
if (
path !==
ENGINE_DIR
) {
log.info(
`Deleting ${i}...`
);
if (
existsSync(
path
)
)
rimraf.sync(
path
);
}
});
}
} else {
log.warning(
"Resetting does not work on manual patches that have a `delete` action, skipping..."
);
}
}
);
let leftovers = new Set();
const { stdout: origFiles } =
await execa(
"git",
[
"clean",
"-e",
"'!*.orig'",
"--dry-run"
],
{ cwd: ENGINE_DIR }
);
const { stdout: rejFiles } =
await execa(
"git",
[
"clean",
"-e",
"'!*.rej'",
"--dry-run"
],
{ cwd: ENGINE_DIR }
);
origFiles
.split("\n")
.map((f) =>
leftovers.add(
f.replace(
/Would remove /,
""
)
)
);
rejFiles
.split("\n")
.map((f) =>
leftovers.add(
f.replace(
/Would remove /,
""
)
)
);
Array.from(leftovers).forEach(
(f: any) => {
const path = resolve(
ENGINE_DIR,
f
);
if (path !== ENGINE_DIR) {
log.info(
`Deleting ${f}...`
);
rimraf.sync(
resolve(ENGINE_DIR, f)
);
}
}
);
log.success("Reset successfully.");
log.info(
"Next time you build, it may need to recompile parts of the program because the cache was invalidated."
);
}
})
.catch((e) => e);
} catch (e) {}
};

36
src/commands/run.ts Normal file
View file

@ -0,0 +1,36 @@
import { existsSync, readdirSync } from "fs";
import { resolve } from "path";
import { bin_name, log } from "..";
import { ENGINE_DIR } from "../constants";
import { dispatch } from "../utils";
export const run = async (chrome?: string) => {
const dirs = readdirSync(ENGINE_DIR);
const objDirname: any = dirs.find((dir) => {
return dir.startsWith("obj-");
});
if (!objDirname) {
throw new Error(
"Dot Browser needs to be built before you can do this."
);
}
const objDir = resolve(ENGINE_DIR, objDirname);
if (existsSync(objDir)) {
dispatch(
"./mach",
["run"].concat(
chrome ? ["-chrome", chrome] : []
),
ENGINE_DIR,
true,
true
);
} else {
log.error(
`Unable to locate any built binaries.\nRun |${bin_name} build| to initiate a build.`
);
}
};

View file

@ -0,0 +1,62 @@
import execa from "execa";
import {
existsSync,
readFileSync,
writeFileSync
} from "fs-extra";
import { resolve } from "path";
import { log } from "..";
export const setBranch = async (branch: string) => {
if (
!existsSync(
resolve(
process.cwd(),
".dotbuild",
"metadata"
)
)
) {
return log.error(
"Cannot find metadata, aborting..."
);
}
const metadata = JSON.parse(
readFileSync(
resolve(
process.cwd(),
".dotbuild",
"metadata"
),
"utf-8"
)
);
try {
await execa("git", [
"rev-parse",
"--verify",
branch
]);
metadata.branch = branch;
writeFileSync(
resolve(
process.cwd(),
".dotbuild",
"metadata"
),
JSON.stringify(metadata)
);
log.success(
`Default branch is at \`${branch}\`.`
);
} catch (e) {
return log.error(
`Branch with name \`${branch}\` does not exist.`
);
}
};

12
src/commands/status.ts Normal file
View file

@ -0,0 +1,12 @@
import { existsSync } from "fs";
import { log } from "..";
import { ENGINE_DIR } from "../constants";
import { dispatch } from "../utils";
export const status = async () => {
if (existsSync(ENGINE_DIR)) {
dispatch("git", ["status"], ENGINE_DIR, true);
} else {
log.error(`Unable to locate src directory.`);
}
};

10
src/commands/test.ts Normal file
View file

@ -0,0 +1,10 @@
import { resolve } from "path";
import { dispatch } from "../utils";
export const test = async () => {
dispatch(
"yarn",
["test"],
resolve(process.cwd(), "src", "dot")
);
};

51
src/constants/index.ts Normal file
View file

@ -0,0 +1,51 @@
import execa from "execa";
import { resolve } from "path";
export const BUILD_TARGETS = [
"linux",
"windows",
"macos"
];
export const ARCHITECTURE = ["i686", "x86_64"];
export const PATCH_ARGS = [
"--ignore-space-change",
"--ignore-whitespace",
"--verbose"
];
export const ENGINE_DIR = resolve(
process.cwd(),
"engine"
);
export const SRC_DIR = resolve(process.cwd(), "src");
export const PATCHES_DIR = resolve(
process.cwd(),
"patches"
);
export const COMMON_DIR = resolve(
process.cwd(),
"common"
);
export const CONFIGS_DIR = resolve(
process.cwd(),
"configs"
);
export let CONFIG_GUESS: any = null;
try {
CONFIG_GUESS = execa.commandSync(
"./build/autoconf/config.guess",
{ cwd: ENGINE_DIR }
).stdout;
} catch (e) {}
export const OBJ_DIR = resolve(
ENGINE_DIR,
`obj-${CONFIG_GUESS}`
);
export const FTL_STRING_LINE_REGEX =
/(([a-zA-Z0-9\-]*|\.[a-z\-]*) =(.*|\.)|\[[a-zA-Z0-9]*\].*(\n\s?\s?})?|\*\[[a-zA-Z0-9]*\] .*(\n\s?\s?})?)/gm;

267
src/controllers/patch.ts Normal file
View file

@ -0,0 +1,267 @@
import chalk from "chalk";
import execa from "execa";
import {
existsSync,
rmdirSync,
rmSync,
statSync
} from "fs-extra";
import { resolve } from "path";
import readline from "readline";
import { log } from "..";
import {
ENGINE_DIR,
PATCH_ARGS,
SRC_DIR
} from "../constants";
import { copyManual } from "../utils";
class Patch {
public name: string;
public action: string;
public src: string | string[];
public type: "file" | "manual";
public status: number[];
public markers?: {
[key: string]: [string, string];
};
public indent?: number;
public options: {
minimal?: boolean;
noIgnore?: boolean;
};
private _done: boolean = false;
private error: Error | unknown;
private async applyAsManual() {
return new Promise(async (res, rej) => {
try {
switch (this.action) {
case "copy":
if (typeof this.src == "string") {
copyManual(
this.src,
this.options.noIgnore
);
}
if (Array.isArray(this.src)) {
this.src.forEach((i) => {
copyManual(
i,
this.options.noIgnore
);
});
}
break;
case "delete":
if (typeof this.src == "string") {
if (
!existsSync(
resolve(
ENGINE_DIR,
this.src
)
)
)
return log.error(
`We were unable to delete the file or directory \`${this.src}\` as it doesn't exist in the src directory.`
);
if (
statSync(
resolve(
ENGINE_DIR,
this.src
)
).isDirectory()
) {
rmdirSync(
resolve(
ENGINE_DIR,
this.src
)
);
} else {
rmSync(
resolve(
ENGINE_DIR,
this.src
)
);
}
}
if (Array.isArray(this.src)) {
this.src.forEach((i) => {
if (
!existsSync(
resolve(
ENGINE_DIR,
i
)
)
)
return log.error(
`We were unable to delete the file or directory \`${i}\` as it doesn't exist in the src directory.`
);
if (
statSync(
resolve(
ENGINE_DIR,
i
)
).isDirectory()
) {
rmdirSync(
resolve(
ENGINE_DIR,
i
)
);
} else {
rmSync(
resolve(
ENGINE_DIR,
i
),
{ force: true }
);
}
});
}
break;
}
res(true);
} catch (e) {
rej(e);
}
});
}
private async applyAsPatch() {
return new Promise(async (res, rej) => {
try {
try {
await execa(
"git",
[
"apply",
"-R",
...PATCH_ARGS,
this.src as any
],
{ cwd: ENGINE_DIR }
);
} catch (e) {
null;
}
const { stdout, exitCode } = await execa(
"git",
[
"apply",
...PATCH_ARGS,
this.src as any
],
{ cwd: ENGINE_DIR }
);
if (exitCode == 0) res(true);
else throw stdout;
} catch (e) {
rej(e);
}
});
}
public async apply() {
if (!this.options.minimal) {
log.info(
`${chalk.gray(
`(${this.status[0]}/${this.status[1]})`
)} Applying ${this.name}...`
);
}
try {
if (this.type == "manual")
await this.applyAsManual();
if (this.type == "file")
await this.applyAsPatch();
this.done = true;
} catch (e) {
this.error = e;
this.done = false;
}
}
public get done() {
return this._done;
}
public set done(_: any) {
this._done = _;
if (!this.options.minimal) {
readline.moveCursor(process.stdout, 0, -1);
readline.clearLine(process.stdout, 1);
log.info(
`${chalk.gray(
`(${this.status[0]}/${this.status[1]})`
)} Applying ${this.name}... ${chalk[
this._done ? "green" : "red"
].bold(
this._done ? "Done ✔" : "Error ❗"
)}`
);
}
if (this.error) {
throw this.error;
}
}
constructor({
name,
action,
src,
type,
status,
markers,
indent,
options
}: {
name: string;
action?: string;
src?: string | string[];
type: "file" | "manual";
status: number[];
markers?: {
[key: string]: [string, string];
};
indent?: number;
options: {
minimal?: boolean;
noIgnore?: boolean;
};
}) {
this.name = name;
this.action = action || "";
this.src = src || resolve(SRC_DIR, name);
this.type = type;
this.status = status;
this.markers = markers;
this.indent = indent;
this.options = options;
}
}
export default Patch;

108
src/index.ts Normal file
View file

@ -0,0 +1,108 @@
import chalk from "chalk";
import commander, { Command } from "commander";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { commands } from "./cmds";
import { ENGINE_DIR } from "./constants";
import Log from "./log";
import { shaCheck } from "./middleware/sha-check";
import { updateCheck } from "./middleware/update-check";
import { errorHandler } from "./utils";
const program = new Command();
export let log = new Log();
program
.storeOptionsAsProperties(false)
.passCommandToAction(false);
const { dot, firefox, melon } =
require("../package.json").versions;
let reportedFFVersion;
if (
existsSync(
resolve(
ENGINE_DIR,
"browser",
"config",
"version.txt"
)
)
) {
const version = readFileSync(
resolve(
ENGINE_DIR,
"browser",
"config",
"version.txt"
),
"utf-8"
).replace(/\n/g, "");
if (version !== firefox) reportedFFVersion = version;
}
export const bin_name = "melon";
program.version(`
\t${chalk.bold("Dot Browser")} ${dot}
\t${chalk.bold("Firefox")} ${firefox} ${
reportedFFVersion
? `(being reported as ${reportedFFVersion})`
: ``
}
\t${chalk.bold("Melon")} ${melon}
${
reportedFFVersion
? `Mismatch detected between expected Firefox version and the actual version.
You may have downloaded the source code using a different version and
then switched to another branch.`
: ``
}
`);
program.name(bin_name);
commands.forEach((command) => {
if (command.flags) {
if (
command.flags.platforms &&
!command.flags.platforms.includes(
process.platform
)
) {
return;
}
}
const _cmd = commander.command(command.cmd);
_cmd.description(command.description);
command?.aliases?.forEach((alias) => {
_cmd.alias(alias);
});
command?.options?.forEach((opt) => {
_cmd.option(opt.arg, opt.description);
});
_cmd.action(async (...args: any) => {
await shaCheck(command.cmd);
await updateCheck();
command.controller(...args);
});
program.addCommand(_cmd);
});
process.on("uncaughtException", errorHandler);
process.on("unhandledException", (err) =>
errorHandler(err, true)
);
program.parse(process.argv);

9
src/interfaces/patch.ts Normal file
View file

@ -0,0 +1,9 @@
export interface IPatch {
name: string;
action: string;
src: string | string[];
markers?: {
[key: string]: [string, string];
};
indent?: number;
}

70
src/log.ts Normal file
View file

@ -0,0 +1,70 @@
import chalk from "chalk";
class Log {
private startTime: number;
constructor() {
const d = new Date();
this.startTime = d.getTime();
}
getDiff() {
const d = new Date();
const currentTime = d.getTime();
const elapsedTime = currentTime - this.startTime;
var secs = Math.floor((elapsedTime / 1000) % 60);
var mins = Math.floor(
(elapsedTime / (60 * 1000)) % 60
);
var hours = Math.floor(
(elapsedTime / (60 * 60 * 1000)) % 24
);
const format = (r: number) => {
return r.toString().length == 1 ? "0" + r : r;
};
return `${format(hours)}:${format(mins)}:${format(
secs
)}`;
}
info(...args: any[]) {
console.info(
chalk.blueBright.bold(this.getDiff()),
...args
);
}
warning(...args: any[]) {
console.info(
chalk.yellowBright.bold(" WARNING"),
...args
);
}
hardWarning(...args: any[]) {
console.info(
"",
chalk.bgRed.bold("WARNING"),
...args
);
}
success(...args: any[]) {
console.log(
`\n${chalk.greenBright.bold("SUCCESS")}`,
...args
);
}
error(...args: any[]) {
throw new Error(...args);
}
}
export default Log;

32
src/manual-patches.ts Normal file
View file

@ -0,0 +1,32 @@
import { sync } from "glob";
import { SRC_DIR } from "./constants";
import { IPatch } from "./interfaces/patch";
let files = sync("**/*", {
nodir: true,
cwd: SRC_DIR
}).filter(
(f) =>
!(
f.endsWith(".patch") ||
f.split("/").includes("node_modules")
)
);
const manualPatches: IPatch[] = [];
files.map((i) => {
const group = i.split("/")[0];
if (!manualPatches.find((m) => m.name == group)) {
manualPatches.push({
name: group,
action: "copy",
src: files.filter(
(f) => f.split("/")[0] == group
)
});
}
});
export default manualPatches;

View file

@ -0,0 +1,53 @@
import execa from "execa";
import { existsSync, readFileSync } from "fs-extra";
import { resolve } from "path";
import { bin_name, log } from "..";
const blacklistedCommands = [
"reset",
"init",
"set-branch"
];
export const shaCheck = async (command: string) => {
if (
blacklistedCommands.filter((c) =>
command.startsWith(c)
).length !== 0 ||
!existsSync(
resolve(
process.cwd(),
".dotbuild",
"metadata"
)
)
)
return;
const metadata = JSON.parse(
readFileSync(
resolve(
process.cwd(),
".dotbuild",
"metadata"
),
"utf-8"
)
);
const { stdout: currentBranch } = await execa("git", [
"branch",
"--show-current"
]);
if (metadata && metadata.branch) {
if (metadata.branch !== currentBranch) {
log.warning(`The current branch \`${currentBranch}\` differs from the original branch \`${metadata.branch}\`.
\t If you are changing the Firefox version, you will need to reset the tree
\t with |${bin_name} reset --hard| and then |${bin_name} download|.
\t Or you can change the default branch by typing |${bin_name} set-branch <branch>|.`);
}
}
};

View file

@ -0,0 +1,31 @@
import axios from "axios";
import { log } from "../";
const pjson = require("../../package.json");
export const updateCheck = async () => {
const firefoxVersion =
pjson.versions["firefox-display"];
try {
const { data } = await axios.get(
`https://product-details.mozilla.org/1.0/firefox_history_major_releases.json`,
{ timeout: 1000 }
);
if (data) {
let version =
Object.keys(data)[
Object.keys(data).length - 1
];
if (
firefoxVersion &&
version !== firefoxVersion
)
log.warning(
`Latest version of Firefox (${version}) does not match frozen version (${firefoxVersion}).`
);
}
} catch (e) {}
};

19
src/types.d.ts vendored Normal file
View file

@ -0,0 +1,19 @@
export interface Cmd {
cmd: string;
description: string;
controller: (...args: any) => void;
options?: CmdOption[];
aliases?: string[];
flags?: {
platforms?: CmdFlagPlatform[];
};
}
export interface CmdOption {
arg: string;
description: string;
}
export type CmdFlagPlatform = NodeJS.Platform;

5
src/utils/delay.ts Normal file
View file

@ -0,0 +1,5 @@
export const delay = (delay: number) => {
return new Promise((resolve) => {
setTimeout(() => resolve(true), delay);
});
};

49
src/utils/dispatch.ts Normal file
View file

@ -0,0 +1,49 @@
import execa from "execa";
import { log } from "..";
const handle = (data: any, killOnError?: boolean) => {
const d = data.toString();
d.split("\n").forEach((line: any) => {
if (line.length !== 0)
log.info(
line.replace(/\s\d{1,5}:\d\d\.\d\d /g, "")
);
});
if (killOnError) {
log.error("Command failed. See error above.");
process.exit(1);
}
};
export const dispatch = (
cmd: string,
args?: any[],
cwd?: string,
noLog?: boolean,
killOnError?: boolean
) => {
return new Promise((resolve, reject) => {
process.env.MACH_USE_SYSTEM_PYTHON = "true";
const proc = execa(cmd, args, {
cwd: cwd ? cwd : process.cwd(),
env: process.env
});
proc.stdout?.on("data", (d) => handle(d));
proc.stderr?.on("data", (d) => handle(d));
proc.stdout?.on("error", (d) =>
handle(d, killOnError)
);
proc.stderr?.on("error", (d) =>
handle(d, killOnError)
);
proc.on("exit", () => {
resolve(true);
});
});
};

View file

@ -0,0 +1,45 @@
import chalk from "chalk";
import { readFileSync } from "fs-extra";
import { resolve } from "path";
import { log } from "..";
export const errorHandler = (
err: Error,
isUnhandledRej: boolean
) => {
let cc = readFileSync(
resolve(process.cwd(), ".dotbuild", "command"),
"utf-8"
);
cc = cc.replace(/(\r\n|\n|\r)/gm, "");
console.log(
`\n ${chalk.redBright.bold(
"ERROR"
)} An error occurred while running command ["${cc
.split(" ")
.join('", "')}"]:`
);
console.log(
`\n\t`,
isUnhandledRej
? err.toString().replace(/\n/g, "\n\t ")
: err.message.replace(/\n/g, "\n\t ")
);
if (err.stack || isUnhandledRej) {
const stack: any = err.stack?.split("\n");
stack.shift();
stack.shift();
console.log(
`\t`,
stack
.join("\n")
.replace(/(\r\n|\n|\r)/gm, "")
.replace(/ at /g, "\n\t • ")
);
}
console.log();
log.info("Exiting due to error.");
process.exit(1);
};

66
src/utils/import.ts Normal file
View file

@ -0,0 +1,66 @@
import {
appendFileSync,
ensureSymlink,
lstatSync,
readFileSync
} from "fs-extra";
import { resolve } from "path";
import rimraf from "rimraf";
import { ENGINE_DIR, SRC_DIR } from "../constants";
const getChunked = (location: string) => {
return location.replace(/\\/g, "/").split("/");
};
export const copyManual = (
name: string,
noIgnore?: boolean
) => {
try {
try {
if (
!lstatSync(
resolve(
ENGINE_DIR,
...getChunked(name)
)
).isSymbolicLink()
) {
rimraf.sync(
resolve(
ENGINE_DIR,
...getChunked(name)
)
);
}
} catch (e) {}
ensureSymlink(
resolve(SRC_DIR, ...getChunked(name)),
resolve(ENGINE_DIR, ...getChunked(name))
);
if (!noIgnore) {
const gitignore = readFileSync(
resolve(ENGINE_DIR, ".gitignore"),
"utf-8"
);
if (
!gitignore.includes(
getChunked(name).join("/")
)
)
appendFileSync(
resolve(ENGINE_DIR, ".gitignore"),
`\n${getChunked(name).join("/")}`
);
}
return;
} catch (e) {
console.log(e);
process.exit(0);
// return e;
}
};

6
src/utils/index.ts Normal file
View file

@ -0,0 +1,6 @@
export * from "./delay";
export * from "./dispatch";
export * from "./error-handler";
export * from "./import";
export * from "./version";
export * from "./write-metadata";

11
src/utils/version.ts Normal file
View file

@ -0,0 +1,11 @@
import axios from "axios";
export const getLatestFF = async () => {
const { data } = await axios.get(
`https://product-details.mozilla.org/1.0/firefox_history_major_releases.json`
);
return Object.keys(data)[
Object.keys(data).length - 1
];
};

View file

@ -0,0 +1,26 @@
import execa from "execa";
import { writeFileSync } from "fs-extra";
import { resolve } from "path";
const pjson = require("../../package.json");
export const writeMetadata = async () => {
const { stdout: sha } = await execa("git", [
"rev-parse",
"HEAD"
]);
const { stdout: branch } = await execa("git", [
"branch",
"--show-current"
]);
writeFileSync(
resolve(process.cwd(), ".dotbuild", "metadata"),
JSON.stringify({
sha,
branch,
birth: Date.now(),
versions: pjson.versions
})
);
};