notion-enhancer/scripts/enhance-desktop-app.mjs

144 lines
5.2 KiB
JavaScript
Executable File

/**
* notion-enhancer
* (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
import os from "node:os";
import fsp from "node:fs/promises";
import { resolve } from "node:path";
import { existsSync } from "node:fs";
import { execSync } from "node:child_process";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import asar from "@electron/asar";
import patch from "./patch-desktop-app.mjs";
const nodeRequire = createRequire(import.meta.url),
platform =
process.platform === "linux" &&
os.release().toLowerCase().includes("microsoft")
? "wsl"
: process.platform,
getEnv = (name) => {
if (platform !== "wsl" || process.env[name]) return process.env[name];
// read windows environment variables and convert
// windows paths to paths mounted in the wsl fs
const pipe = { encoding: "utf8", stdio: "pipe" },
value = execSync(`cmd.exe /c echo %${name}%`, pipe).trim(),
isAbsolutePath = /^[a-zA-Z]:[\\\/]/.test(value),
isSystemPath = /^[\\\/]/.test(value);
if (isAbsolutePath) {
// e.g. C:\Program Files
const drive = value[0].toLowerCase(),
path = value.slice(2).replace(/\\/g, "/");
process.env[name] = `/mnt/${drive}${path}`;
} else if (isSystemPath) {
// e.g. \Program Files
const drive = getEnv("SYSTEMDRIVE")[0].toLowerCase(),
path = value.replace(/\\/g, "/");
process.env[name] = `/mnt/${drive}${path}`;
} else process.env[name] = value;
return process.env[name];
};
let __notionResources;
const setNotionPath = (path) => {
// sets notion resource path to user provided value
// e.g. with the --path cli option
__notionResources = path;
},
getResourcePath = (...paths) => {
if (__notionResources) return resolve(__notionResources, ...paths);
// prettier-ignore
for (const [platforms, notionResources] of [
[['win32', 'wsl'], resolve(`${getEnv("LOCALAPPDATA")}/Programs/Notion/resources`)],
[['win32', 'wsl'], resolve(`${getEnv("PROGRAMW6432")}/Notion/resources`)],
[['darwin'], `/Users/${getEnv("USER")}/Applications/Notion.app/Contents/Resources`],
[['darwin'], "/Applications/Notion.app/Contents/Resources"],
[['linux'], "/opt/notion-app"],
]) {
if (!platforms.includes(platform)) continue;
if (!existsSync(notionResources)) continue;
__notionResources = notionResources;
return resolve(__notionResources, ...paths);
}
},
extractFile = (path) => {
const archive = getResourcePath("app.asar");
return asar.extractFile(archive, path);
};
const getInsertPath = (...paths) => {
return "node_modules/notion-enhancer/" + paths.join("/");
},
getInsertVersion = () => {
try {
const manifest = extractFile(getInsertPath("package.json")).toString();
return JSON.parse(manifest).version;
} catch {
return null;
}
};
const backupApp = async () => {
const archive = getResourcePath("app.asar");
if (!existsSync(archive)) return false;
await fsp.cp(archive, archive + ".bak");
return true;
},
restoreApp = async () => {
const archive = getResourcePath("app.asar");
if (!existsSync(archive + ".bak")) return false;
await fsp.rename(archive + ".bak", archive);
return true;
},
enhanceApp = async (debug = false) => {
const app = getResourcePath("app"),
archive = getResourcePath("app.asar");
if (!existsSync(archive)) return false;
if (existsSync(app)) await fsp.rm(app, { recursive: true, force: true });
await fsp.mkdir(app);
// extract archive to folder and apply patches
for (let file of asar.listPackage(archive)) {
file = file.replace(/^\//g, "");
const stat = asar.statFile(archive, file),
isFolder = !!stat.files,
isSymlink = !!stat.link,
isExecutable = stat.executable,
appPath = resolve(app, file);
if (isFolder) {
await fsp.mkdir(appPath);
} else if (isSymlink) {
await fsp.symlink(appPath, resolve(app, link));
} else {
await fsp.writeFile(appPath, patch(file, extractFile(file)));
if (isExecutable) await fsp.chmod(appPath, "755");
}
}
// insert the notion-enhancer/src folder into notion's node_modules
const insertSrc = fileURLToPath(new URL("../src", import.meta.url)),
insertDest = resolve(app, getInsertPath());
await fsp.cp(insertSrc, insertDest, { recursive: true });
// create package.json with cli-specific fields removed
const insertManifest = resolve(insertDest, "package.json"),
manifest = { ...nodeRequire("../package.json"), main: "init.js" },
excludes = ["bin", "type", "scripts", "engines", "dependencies"];
for (const key of excludes) delete manifest[key];
await fsp.writeFile(insertManifest, JSON.stringify(manifest));
// re-package enhanced sources into executable archive
await asar.createPackage(app, archive);
// cleanup extracted files unless in debug mode
if (!debug) await fsp.rm(app, { recursive: true });
return true;
};
export {
backupApp,
restoreApp,
enhanceApp,
getInsertVersion,
getResourcePath,
setNotionPath,
};