mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-04 04:39:03 +00:00
feat(cli): ported remove cmd to new cli
+ more detailed error messages inc. alternative instructions for removal if not backup is found
This commit is contained in:
parent
a7861be39a
commit
ebf15dbfb9
243
bin.mjs
243
bin.mjs
@ -10,13 +10,74 @@ import arg from "arg";
|
||||
import chalk from "chalk-template";
|
||||
import os from "node:os";
|
||||
import { createRequire } from "node:module";
|
||||
import { checkEnhancementStatus, setNotionPath } from "./scripts/electron.mjs";
|
||||
import {
|
||||
getResourcePath,
|
||||
getAppPath,
|
||||
getBackupPath,
|
||||
getCachePath,
|
||||
checkEnhancementVersion,
|
||||
setNotionPath,
|
||||
unpackApp,
|
||||
applyEnhancements,
|
||||
takeBackup,
|
||||
restoreBackup,
|
||||
removeCache,
|
||||
} from "./scripts/enhance-desktop-app.mjs";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
let __quiet = false;
|
||||
let __quiet, __spinner;
|
||||
const nodeRequire = createRequire(import.meta.url),
|
||||
manifest = nodeRequire("./package.json"),
|
||||
stdout = (...args) => __quiet || process.stdout.write(chalk(...args)),
|
||||
stdoutRaw = (value) => __quiet || console.log(value);
|
||||
print = (...args) => __quiet || process.stdout.write(chalk(...args)),
|
||||
printObject = (value) => __quiet || console.dir(value, { depth: null });
|
||||
|
||||
const hideCursor = () => process.stdout.write("\x1b[?25l"),
|
||||
showCursor = () => process.stdout.write("\x1b[?25h"),
|
||||
stopSpinner = () => __spinner?.stop(),
|
||||
startSpinner = () => {
|
||||
// cleanup prev spinner
|
||||
stopSpinner();
|
||||
let i = 0;
|
||||
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
||||
interval = setInterval(() => __spinner.step(), 80);
|
||||
// prevent backspace removing existing stdout
|
||||
print` `;
|
||||
__spinner = {
|
||||
step() {
|
||||
i++;
|
||||
// overwrite spinner with next frame
|
||||
print`\b\b\b {bold.yellow ${frames[i % frames.length]}} `;
|
||||
hideCursor();
|
||||
},
|
||||
stop() {
|
||||
clearInterval(interval);
|
||||
// overwrite spinner with arrow on completion
|
||||
print`\b\b\b {bold.yellow →}\n`;
|
||||
showCursor();
|
||||
},
|
||||
};
|
||||
},
|
||||
readStdin = () => {
|
||||
return new Promise((res) => {
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.once("data", (key) => {
|
||||
process.stdin.pause();
|
||||
res(key);
|
||||
});
|
||||
});
|
||||
},
|
||||
promptForValue = async (prompt, values) => {
|
||||
let input;
|
||||
// prevent line clear remove existing stdout
|
||||
print`\n`;
|
||||
do {
|
||||
// clear line and continue prompting until valid input is received
|
||||
print`\x1b[1A\r\x1b[K${prompt}`;
|
||||
input = (await readStdin()).trim();
|
||||
} while (!values.includes(input));
|
||||
return input;
|
||||
};
|
||||
|
||||
const commands = [
|
||||
// ["command", "description"]
|
||||
@ -25,7 +86,7 @@ const commands = [
|
||||
["check", "check the current state of the notion app"],
|
||||
],
|
||||
options = [
|
||||
// ["comma, separated, aliases", [type, "description"]]
|
||||
// ["alias, option=example", [type, "description"]]
|
||||
[
|
||||
"--path=</path/to/notion/resources>",
|
||||
[String, "provide notion installation location (defaults to auto-detected)"],
|
||||
@ -34,9 +95,9 @@ const commands = [
|
||||
["--overwrite", [Boolean, ""]],
|
||||
["-y, --yes", [Boolean, 'skip prompts; assume "yes" and run non-interactively']],
|
||||
["-n, --no", [Boolean, 'skip prompts; assume "no" and run non-interactively']],
|
||||
["-q, --quiet", [Boolean, "hide all output"]],
|
||||
["-q, --quiet", [Boolean, 'skip prompts and hide all output; assume "no"']],
|
||||
["-d, --debug", [Boolean, "show detailed error messages"]],
|
||||
["-j, --json", [Boolean, "display json output"]],
|
||||
["-j, --json", [Boolean, "display json output (where applicable)"]],
|
||||
["-h, --help", [Boolean, "display usage information"]],
|
||||
["-v, --version", [Boolean, "display version number"]],
|
||||
],
|
||||
@ -52,24 +113,54 @@ const commands = [
|
||||
}
|
||||
}
|
||||
return args;
|
||||
},
|
||||
compileOptsToJsonOutput = () => {
|
||||
// the structure used to define options above
|
||||
// is convenient and compact, but requires additional
|
||||
// parsing to understand. this function processes
|
||||
// options into a more explicitly defined structure
|
||||
return options.map(([opt, [type, description]]) => {
|
||||
const option = {
|
||||
aliases: opt.split(", ").map((alias) => alias.split("=")[0]),
|
||||
type,
|
||||
description,
|
||||
},
|
||||
example = opt
|
||||
.split(", ")
|
||||
.map((alias) => alias.split("=")[1])
|
||||
.find((value) => value);
|
||||
if (example) option.example = example;
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
const args = arg(compileOptsToArgSpec(options)),
|
||||
printHelp = () => {
|
||||
const cmdPad = Math.max(...commands.map(([cmd]) => cmd.length)),
|
||||
optPad = Math.max(...options.map((opt) => opt[0].length)),
|
||||
parseCmd = (cmd) => chalk` ${cmd[0].padEnd(cmdPad)} {grey :} ${cmd[1]}`,
|
||||
parseOpt = (opt) => chalk` ${opt[0].padEnd(optPad)} {grey :} ${opt[1][1]}`;
|
||||
stdout`{bold.rgb(245,245,245) ${manifest.name} v${manifest.version}}
|
||||
{grey ${manifest.homepage}}
|
||||
\n{bold.rgb(245,245,245) USAGE}
|
||||
{yellow $} ${manifest.name} <command> [options]
|
||||
\n{bold.rgb(245,245,245) COMMANDS}\n${commands.map(parseCmd).join("\n")}
|
||||
\n{bold.rgb(245,245,245) OPTIONS}\n${options.map(parseOpt).join("\n")}\n`;
|
||||
const { name, version, homepage } = manifest,
|
||||
usage = `${name} <command> [options]`;
|
||||
if (args["--json"]) {
|
||||
printObject({
|
||||
name,
|
||||
version,
|
||||
homepage,
|
||||
usage,
|
||||
commands: Object.fromEntries(commands),
|
||||
options: compileOptsToJsonOutput(),
|
||||
});
|
||||
} else {
|
||||
const cmdPad = Math.max(...commands.map(([cmd]) => cmd.length)),
|
||||
optPad = Math.max(...options.map((opt) => opt[0].length)),
|
||||
parseCmd = (cmd) => chalk` ${cmd[0].padEnd(cmdPad)} {grey :} ${cmd[1]}`,
|
||||
parseOpt = (opt) => chalk` ${opt[0].padEnd(optPad)} {grey :} ${opt[1][1]}`;
|
||||
print`{bold.whiteBright ${name} v${version}}\n{grey ${homepage}}
|
||||
\n{bold.whiteBright USAGE}\n${name} <command> [options]
|
||||
\n{bold.whiteBright COMMANDS}\n${commands.map(parseCmd).join("\n")}
|
||||
\n{bold.whiteBright OPTIONS}\n${options.map(parseOpt).join("\n")}\n`;
|
||||
}
|
||||
},
|
||||
printVersion = () => {
|
||||
if (args["--json"]) {
|
||||
stdoutRaw({
|
||||
printObject({
|
||||
[manifest.name]: manifest.version,
|
||||
node: process.version.slice(1),
|
||||
platform: process.platform,
|
||||
@ -80,53 +171,105 @@ const args = arg(compileOptsToArgSpec(options)),
|
||||
const enhancerVersion = `${manifest.name}@v${manifest.version}`,
|
||||
nodeVersion = `node@${process.version}`,
|
||||
osVersion = `${process.platform}-${process.arch}/${os.release()}`;
|
||||
stdout`${enhancerVersion} via ${nodeVersion} on ${osVersion}\n`;
|
||||
print`${enhancerVersion} via ${nodeVersion} on ${osVersion}\n`;
|
||||
}
|
||||
};
|
||||
|
||||
if (args["--quiet"]) __quiet = true;
|
||||
if (args["--help"]) [printHelp(), process.exit()];
|
||||
if (args["--version"]) [printVersion(), process.exit()];
|
||||
if (args["--path"]) setNotionPath(args["--path"]);
|
||||
const defaultPromptValue = args["--yes"]
|
||||
? "y"
|
||||
: args["--no"] || args["--quiet"]
|
||||
? "n"
|
||||
: undefined;
|
||||
|
||||
const notionNotFound = `notion installation not found (corrupted or nonexistent)`,
|
||||
enhancerNotApplied = `notion-enhancer not applied (notion installation found)`,
|
||||
onSuccess = chalk`{bold.whiteBright SUCCESS} {green ✔}`,
|
||||
onFail = chalk`{bold.whiteBright FAILURE} {red ✘}`,
|
||||
onCancel = chalk`{bold.whiteBright CANCELLED} {red ✘}`;
|
||||
|
||||
switch (args["_"][0]) {
|
||||
case "apply": {
|
||||
print`{bold.whiteBright [NOTION-ENHANCER] APPLY} `;
|
||||
// const res = await apply(notionPath, {
|
||||
// overwritePrevious: promptRes,
|
||||
// patchPrevious: opts.get("patch") ? true : false,
|
||||
// takeBackup: opts.get("no-backup") ? false : true,
|
||||
// });
|
||||
// if (res) {
|
||||
// log`{bold.whiteBright SUCCESS} {green ✔}`;
|
||||
// } else log`{bold.whiteBright CANCELLED} {red ✘}`;
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
const appPath = getAppPath(),
|
||||
backupPath = getBackupPath(),
|
||||
cachePath = getCachePath(),
|
||||
insertVersion = checkEnhancementVersion();
|
||||
print`{bold.whiteBright [NOTION-ENHANCER] REMOVE}\n`;
|
||||
// restore notion to app.bak or app.asar.bak
|
||||
if (!appPath) {
|
||||
print` {grey * ${notionNotFound}}\n`;
|
||||
} else if (insertVersion) {
|
||||
print` {grey * notion installation found: notion-enhancer v${insertVersion} applied}\n`;
|
||||
if (backupPath) {
|
||||
print` {grey * backup found: restoring}`;
|
||||
startSpinner();
|
||||
await restoreBackup();
|
||||
stopSpinner();
|
||||
} else {
|
||||
print` {grey * backup not found: skipping}\n`;
|
||||
print` {red * to remove the notion-enhancer from notion, uninstall notion and then install}\n`;
|
||||
print` {red a vanilla version of the app from https://www.notion.so/desktop (mac, windows)}\n`;
|
||||
print` {red or ${manifest.homepage}/getting-started/installation (linux)\n}`;
|
||||
}
|
||||
} else print` {grey * ${enhancerNotApplied}: skipping}\n`;
|
||||
// optionally remove ~/.notion-enhancer
|
||||
if (existsSync(cachePath)) {
|
||||
print` {grey * cache found: ${cachePath}}\n`;
|
||||
let deleteCache;
|
||||
const prompt = chalk` {inverse > delete? [Y/n]:} `;
|
||||
if (defaultPromptValue) {
|
||||
deleteCache = defaultPromptValue;
|
||||
print`${prompt}${defaultPromptValue}\n`;
|
||||
} else deleteCache = await promptForValue(prompt, ["Y", "y", "N", "n"]);
|
||||
if (["Y", "y"].includes(deleteCache)) {
|
||||
print` {grey * cache found: removing}`;
|
||||
startSpinner();
|
||||
await removeCache();
|
||||
stopSpinner();
|
||||
} else print` {grey * cache found: keeping}\n`;
|
||||
} else print` {grey * cache not found: skipping}\n`;
|
||||
// failure if backup could not be restored
|
||||
print`${insertVersion && !backupPath ? onFail : onSuccess}\n`;
|
||||
break;
|
||||
}
|
||||
// case "apply": {
|
||||
// stdout`{bold.rgb(245,245,245) [NOTION-ENHANCER] APPLY}`;
|
||||
// const res = await apply(notionPath, {
|
||||
// overwritePrevious: promptRes,
|
||||
// patchPrevious: opts.get("patch") ? true : false,
|
||||
// takeBackup: opts.get("no-backup") ? false : true,
|
||||
// });
|
||||
// if (res) {
|
||||
// log`{bold.rgb(245,245,245) SUCCESS} {green ✔}`;
|
||||
// } else log`{bold.rgb(245,245,245) CANCELLED} {red ✘}`;
|
||||
// break;
|
||||
// }
|
||||
// case "remove": {
|
||||
// log`{bold.rgb(245,245,245) [NOTION-ENHANCER] REMOVE}`;
|
||||
// const res = await remove(notionPath, { delCache: promptRes });
|
||||
// if (res) {
|
||||
// log`{bold.rgb(245,245,245) SUCCESS} {green ✔}`;
|
||||
// } else log`{bold.rgb(245,245,245) CANCELLED} {red ✘}`;
|
||||
// break;
|
||||
// }
|
||||
case "check":
|
||||
const status = checkEnhancementStatus();
|
||||
if (args["--json"]) [stdoutRaw(status), process.exit()];
|
||||
stdout`{bold.rgb(245,245,245) [NOTION-ENHANCER] CHECK:} `;
|
||||
if (manifest.version === status.insertVersion) {
|
||||
stdout`notion-enhancer v${manifest.version} applied.\n`;
|
||||
} else if (status.insertVersion) {
|
||||
stdout`notion-enhancer v${manifest.version} applied != v${status.insertVersion} cli.\n`;
|
||||
} else if (status.appPath) {
|
||||
stdout`notion-enhancer has not been applied (notion installation found).\n`;
|
||||
} else {
|
||||
stdout`notion installation not found (corrupted or nonexistent).\n`;
|
||||
const appPath = getAppPath(),
|
||||
backupPath = getBackupPath(),
|
||||
cachePath = getCachePath(),
|
||||
insertVersion = checkEnhancementVersion();
|
||||
if (args["--json"]) {
|
||||
printObject({
|
||||
appPath,
|
||||
backupPath,
|
||||
cachePath,
|
||||
doesCacheExist: existsSync(cachePath),
|
||||
insertVersion,
|
||||
});
|
||||
process.exit();
|
||||
}
|
||||
print`{bold.whiteBright [NOTION-ENHANCER] CHECK:} `;
|
||||
if (manifest.version === insertVersion) {
|
||||
print`notion-enhancer v${manifest.version} applied\n`;
|
||||
} else if (insertVersion) {
|
||||
print`notion-enhancer v${manifest.version} applied != v${insertVersion} cli\n`;
|
||||
} else if (appPath) {
|
||||
print`${enhancerNotApplied}.\n`;
|
||||
} else print`${notionNotFound}.\n`;
|
||||
break;
|
||||
default:
|
||||
printHelp();
|
||||
|
@ -14,6 +14,7 @@
|
||||
"node": ">=16.x.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.2",
|
||||
"arg": "^5.0.2",
|
||||
"chalk-template": "^0.4.0"
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
import chalk from "chalk-template";
|
||||
import asar from "@electron/asar";
|
||||
import os from "node:os";
|
||||
import { promises as fsp, existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
@ -43,11 +43,11 @@ const nodeRequire = createRequire(import.meta.url),
|
||||
|
||||
const setNotionPath = (path) => {
|
||||
// sets notion resource path to user provided value
|
||||
// e.g. from cli
|
||||
// e.g. with the --path cli option
|
||||
__notionResources = path;
|
||||
},
|
||||
getNotionResources = () => {
|
||||
if (__notionResources) return __notionResources;
|
||||
getResourcePath = (path) => {
|
||||
if (__notionResources) return resolve(`${__notionResources}/${path}`);
|
||||
polyfillWslEnv("LOCALAPPDATA");
|
||||
polyfillWslEnv("PROGRAMW6432");
|
||||
const potentialPaths = [
|
||||
@ -63,48 +63,75 @@ const setNotionPath = (path) => {
|
||||
if (!targetPlatforms.includes(platform)) continue;
|
||||
if (!existsSync(testPath)) continue;
|
||||
__notionResources = testPath;
|
||||
return __notionResources;
|
||||
return resolve(`${__notionResources}/${path}`);
|
||||
}
|
||||
},
|
||||
getEnhancerCache = () => {
|
||||
// prefer unpacked if both exist
|
||||
getAppPath = () => ["app", "app.asar"].map(getResourcePath).find(existsSync),
|
||||
getBackupPath = () => ["app.bak", "app.asar.bak"].map(getResourcePath).find(existsSync),
|
||||
getCachePath = () => {
|
||||
if (__enhancerCache) return __enhancerCache;
|
||||
const home = platform === "wsl" ? polyfillWslEnv("HOMEPATH") : os.homedir();
|
||||
__enhancerCache = resolve(`${home}/.notion-enhancer`);
|
||||
return __enhancerCache;
|
||||
},
|
||||
checkEnhancementVersion = () => {
|
||||
const insertPath = getResourcePath("app/node_modules/notion-enhancer");
|
||||
if (!existsSync(insertPath)) return undefined;
|
||||
const insertManifest = getResourcePath("app/node_modules/notion-enhancer/package.json"),
|
||||
insertVersion = nodeRequire(insertManifest).version;
|
||||
return insertVersion;
|
||||
};
|
||||
|
||||
const checkEnhancementStatus = () => {
|
||||
const resourcePath = (path) => resolve(`${getNotionResources()}/${path}`),
|
||||
doesResourceExist = (path) => existsSync(resourcePath(path));
|
||||
|
||||
const isAppUnpacked = doesResourceExist("app"),
|
||||
isAppPacked = doesResourceExist("app.asar"),
|
||||
isBackupUnpacked = doesResourceExist("app.bak"),
|
||||
isBackupPacked = doesResourceExist("app.asar.bak"),
|
||||
isEnhancerInserted = doesResourceExist("app/node_module/notion-enhancer"),
|
||||
enhancerInsertManifest = isEnhancerInserted
|
||||
? resourcePath("app/node_module/notion-enhancer/package.json")
|
||||
: undefined;
|
||||
|
||||
// prefer unpacked if both exist: extraction is slow
|
||||
return {
|
||||
appPath: isAppUnpacked
|
||||
? resourcePath("app")
|
||||
: isAppPacked
|
||||
? resourcePath("app.asar")
|
||||
: undefined,
|
||||
backupPath: isBackupUnpacked
|
||||
? resourcePath("app.bak")
|
||||
: isBackupPacked
|
||||
? resourcePath("app.asar.bak")
|
||||
: undefined,
|
||||
cachePath: existsSync(getEnhancerCache()) //
|
||||
? getEnhancerCache()
|
||||
: undefined,
|
||||
insertVersion: isEnhancerInserted
|
||||
? nodeRequire(enhancerInsertManifest).version
|
||||
: undefined,
|
||||
const unpackApp = () => {
|
||||
const appPath = getAppPath();
|
||||
if (!appPath || !appPath.endsWith("asar")) return false;
|
||||
asar.extractAll(appPath, appPath.replace(/\.asar$/, ""));
|
||||
return true;
|
||||
},
|
||||
applyEnhancements = () => {
|
||||
const appPath = getAppPath();
|
||||
if (!appPath || appPath.endsWith("asar")) return false;
|
||||
// ...
|
||||
return true;
|
||||
},
|
||||
takeBackup = async () => {
|
||||
const appPath = getAppPath();
|
||||
if (!appPath) return false;
|
||||
const backupPath = getBackupPath();
|
||||
if (backupPath) await fsp.rm(backupPath, { recursive: true });
|
||||
const destPath = `${appPath}.bak`;
|
||||
if (!appPath.endsWith(".asar")) {
|
||||
await fsp.cp(appPath, destPath, { recursive: true });
|
||||
} else await fsp.rename(appPath, destPath);
|
||||
return true;
|
||||
},
|
||||
restoreBackup = async () => {
|
||||
const backupPath = getBackupPath();
|
||||
if (!backupPath) return false;
|
||||
const destPath = backupPath.replace(/\.bak$/, "");
|
||||
if (existsSync(destPath)) await fsp.rm(destPath, { recursive: true });
|
||||
await fsp.rename(backupPath, destPath);
|
||||
const appPath = getAppPath();
|
||||
if (destPath !== appPath) await fsp.rm(appPath, { recursive: true });
|
||||
return true;
|
||||
},
|
||||
removeCache = async () => {
|
||||
if (!existsSync(getCachePath())) return;
|
||||
await fsp.rm(getCachePath());
|
||||
return true;
|
||||
};
|
||||
|
||||
export {
|
||||
getResourcePath,
|
||||
getAppPath,
|
||||
getBackupPath,
|
||||
getCachePath,
|
||||
checkEnhancementVersion,
|
||||
setNotionPath,
|
||||
unpackApp,
|
||||
applyEnhancements,
|
||||
takeBackup,
|
||||
restoreBackup,
|
||||
removeCache,
|
||||
};
|
||||
|
||||
export { checkEnhancementStatus, setNotionPath };
|
Loading…
Reference in New Issue
Block a user