169 lines
4.9 KiB
TypeScript
Executable File
169 lines
4.9 KiB
TypeScript
Executable File
#!/usr/bin/env runts -- --allow-all
|
|
|
|
import {
|
|
execCommandAndStdout,
|
|
execCommandShell,
|
|
existsPath,
|
|
log,
|
|
stringifyPretty,
|
|
term,
|
|
uint8ArrayToHexString,
|
|
writeStringToFile,
|
|
} from "https://script.hatter.ink/@35/deno-commons-mod.ts";
|
|
import {parseArgs} from "jsr:@std/cli/parse-args";
|
|
import {readFileToString} from "https://global.hatter.ink/script/get/@1/deno-commons-1.6.0-mod.ts";
|
|
|
|
async function main() {
|
|
const flags = parseArgs(Deno.args, {
|
|
boolean: [
|
|
"help",
|
|
"no-min",
|
|
"skip-sign",
|
|
"force",
|
|
],
|
|
alias: {
|
|
h: "help",
|
|
S: "skip-sign",
|
|
M: "no-min",
|
|
F: "force",
|
|
},
|
|
});
|
|
|
|
if (flags.help) {
|
|
console.log(term.auto(`Usage:
|
|
[blue][bold]build.ts[//] --help
|
|
[blue][bold]build.ts[//] [-M|--no-min] [-S|--skip-sign] [-F|--force] FILES`));
|
|
return;
|
|
}
|
|
|
|
if (flags._.length === 0) {
|
|
log.error(`No files specified`);
|
|
return;
|
|
}
|
|
flags.min = !flags["no-min"];
|
|
|
|
const files: string[] = flags._.filter((f) => {
|
|
return f.endsWith(".ts") && !f.includes(".bundle.");
|
|
});
|
|
|
|
const filesCount = files.length;
|
|
log.info(`Total ${filesCount} file(s)`);
|
|
for (let i = 0; i < filesCount; i++) {
|
|
const file = files[i];
|
|
if (file.includes(".bundle.") || !file.endsWith(".ts")) {
|
|
log.warn(`Skip bundle file: ${file} #${i + 1} of ${filesCount}`);
|
|
continue;
|
|
}
|
|
log.info(`Building file: ${file} #${i + 1} of ${filesCount}`);
|
|
await buildFile(file, flags);
|
|
}
|
|
}
|
|
|
|
function getBundleFilename(file: string): string {
|
|
const lastIndexOfDot = file.lastIndexOf(".");
|
|
if (lastIndexOfDot === -1) {
|
|
return `${file}.bundle.ts`;
|
|
}
|
|
return `${file.substring(0, lastIndexOfDot)}.bundle.${
|
|
file.substring(lastIndexOfDot + 1)
|
|
}`;
|
|
}
|
|
|
|
async function sha256OfString(input: string): Promise<string> {
|
|
const data = new TextEncoder().encode(input);
|
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
return uint8ArrayToHexString(new Uint8Array(hashBuffer));
|
|
}
|
|
|
|
interface SourceFileMeta {
|
|
sha256: string;
|
|
builtTime: number;
|
|
min: boolean;
|
|
}
|
|
|
|
async function writeBundleSourceSha256(
|
|
sourceFile: string,
|
|
bundleFile: string,
|
|
flags: any,
|
|
) {
|
|
const sourceFileSha256 = `${bundleFile}.source-meta`;
|
|
const sourceFileContent = await readFileToString(sourceFile);
|
|
const sourceFileContentSha256 = await sha256OfString(sourceFileContent);
|
|
await writeStringToFile(
|
|
sourceFileSha256,
|
|
stringifyPretty({
|
|
sha256: sourceFileContentSha256,
|
|
builtTime: Date.now(),
|
|
min: flags.min,
|
|
} as SourceFileMeta),
|
|
);
|
|
}
|
|
|
|
async function checkBundleSourceSha256(
|
|
sourceFile: string,
|
|
bundleFile: string,
|
|
flags: any,
|
|
) {
|
|
const sourceFileSha256 = `${bundleFile}.source-meta`;
|
|
if (!await existsPath(sourceFileSha256)) {
|
|
return false;
|
|
}
|
|
const sourceFileSha256Content = await readFileToString(sourceFileSha256);
|
|
const sourceFileSha256Meta = JSON.parse(
|
|
sourceFileSha256Content,
|
|
) as SourceFileMeta;
|
|
if (sourceFileSha256Meta.min !== flags.min) {
|
|
return false;
|
|
}
|
|
const sourceFileContent = await readFileToString(sourceFile);
|
|
const sourceFileContentSha256 = await sha256OfString(sourceFileContent);
|
|
return sourceFileContentSha256 === sourceFileSha256Meta.sha256;
|
|
}
|
|
|
|
async function buildFile(file: string, flags: any) {
|
|
const denoCmd = "deno";
|
|
const bundleFile = getBundleFilename(file);
|
|
const denoBundleArgs = ["bundle"];
|
|
if (flags.min) {
|
|
denoBundleArgs.push("--minify");
|
|
}
|
|
denoBundleArgs.push(file);
|
|
denoBundleArgs.push("-o");
|
|
denoBundleArgs.push(bundleFile);
|
|
|
|
const isSourceFileMatches = await checkBundleSourceSha256(
|
|
file,
|
|
bundleFile,
|
|
flags,
|
|
);
|
|
if (isSourceFileMatches && !flags.force) {
|
|
log.info(`Check file ${file} sha256 matches, skip build`);
|
|
return;
|
|
}
|
|
if (isSourceFileMatches) {
|
|
log.info(`Force build file is on`);
|
|
}
|
|
log.debug("Run deno build: ", denoCmd, denoBundleArgs);
|
|
await execCommandShell(denoCmd, denoBundleArgs);
|
|
if (flags["skip-sign"]) {
|
|
log.warn(`Skip signature for file: ${bundleFile}`);
|
|
} else {
|
|
const signScriptUserPin = execCommandAndStdout("keyring.rs", [
|
|
"-gRU",
|
|
"yubikey4white:user-pin",
|
|
]);
|
|
await execCommandShell("script-sign.rs", [
|
|
"--pin",
|
|
signScriptUserPin,
|
|
bundleFile,
|
|
]);
|
|
}
|
|
await execCommandShell("chmod", ["+x", bundleFile]);
|
|
await writeBundleSourceSha256(file, bundleFile, flags);
|
|
}
|
|
|
|
await main();
|
|
|
|
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260209T010054+08:00.MEYCIQD6vk5cA8zttDLbs4Le
|
|
// V4Yd9z5bjTaQzw3TikZibSEI0gIhAJyPmDv0b5Vh0fFPxh3SMj3F3MWtb/23Frq31pqfOtCB
|