chore!: store config in electron-defined userData folder instead of home folder

- cli can no longer detect where config will be, so config-related cli prompts have been removed
- should avoid file conflicts and enable portable builds
- users can export their data in the more readable .json format from the profiles section of the menu and no longer require direct access to the config file
This commit is contained in:
dragonwocky 2023-01-29 16:07:26 +11:00
parent f61ac3d8d9
commit f999969c13
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
4 changed files with 74 additions and 90 deletions

28
bin.mjs
View File

@ -13,14 +13,12 @@ import { createRequire } from "node:module";
import { import {
getAppPath, getAppPath,
getBackupPath, getBackupPath,
getConfigPath,
checkEnhancementVersion, checkEnhancementVersion,
setNotionPath, setNotionPath,
unpackApp, unpackApp,
applyEnhancements, applyEnhancements,
takeBackup, takeBackup,
restoreBackup, restoreBackup,
removeConfig,
} from "./scripts/enhance-desktop-app.mjs"; } from "./scripts/enhance-desktop-app.mjs";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
const nodeRequire = createRequire(import.meta.url), const nodeRequire = createRequire(import.meta.url),
@ -218,7 +216,6 @@ try {
const appPath = getAppPath(), const appPath = getAppPath(),
backupPath = getBackupPath(), backupPath = getBackupPath(),
configPath = getConfigPath(),
insertVersion = checkEnhancementVersion(); insertVersion = checkEnhancementVersion();
const messages = { const messages = {
@ -239,10 +236,6 @@ try {
"manual-removal-instructions": `to remove the notion-enhancer from notion, uninstall notion and "manual-removal-instructions": `to remove the notion-enhancer from notion, uninstall notion and
then install a vanilla version of the app from https://www.notion.so/desktop (mac, then install a vanilla version of the app from https://www.notion.so/desktop (mac,
windows) or ${manifest.homepage}/getting-started/installation (linux)`, windows) or ${manifest.homepage}/getting-started/installation (linux)`,
"config-found": `config found`,
"config-not-found": `config not found: nothing to remove`,
"prompt-config-removal": `remove?`,
}; };
const SUCCESS = chalk`{bold.whiteBright SUCCESS} {green ✔}`, const SUCCESS = chalk`{bold.whiteBright SUCCESS} {green ✔}`,
FAILURE = chalk`{bold.whiteBright FAILURE} {red ✘}`, FAILURE = chalk`{bold.whiteBright FAILURE} {red ✘}`,
@ -316,9 +309,8 @@ try {
stopSpinner(); stopSpinner();
print` {grey * ${messages["version-applied"]}}\n`; print` {grey * ${messages["version-applied"]}}\n`;
return SUCCESS; return SUCCESS;
}; },
interactiveRemoveEnhancements = async () => {
const interactiveRemoveEnhancements = async () => {
if (!appPath) { if (!appPath) {
// notion not installed // notion not installed
print` {red * ${messages["notion-not-found"]}}\n`; print` {red * ${messages["notion-not-found"]}}\n`;
@ -331,19 +323,6 @@ try {
} }
print` {grey * ${messages["notion-found"]}: ${messages["not-applied"]}}\n`; print` {grey * ${messages["notion-found"]}: ${messages["not-applied"]}}\n`;
return SUCCESS; return SUCCESS;
},
promptConfigRemoval = async () => {
if (existsSync(configPath)) {
print` {grey * ${messages["config-found"]}: ${configPath}}\n`;
// prettier-ignore
const promptRemoval = await promptConfirmation(messages["prompt-config-removal"]);
if (["Y", "y"].includes(promptRemoval)) {
print` `;
startSpinner();
await removeConfig();
stopSpinner();
} else print`\n`;
} else print` {grey * ${messages["config-not-found"]}}\n`;
}; };
switch (args["_"][0]) { switch (args["_"][0]) {
@ -357,7 +336,6 @@ try {
case "remove": { case "remove": {
print`{bold.whiteBright [NOTION-ENHANCER] REMOVE}\n`; print`{bold.whiteBright [NOTION-ENHANCER] REMOVE}\n`;
const res = await interactiveRemoveEnhancements(); const res = await interactiveRemoveEnhancements();
await promptConfigRemoval();
print`${res}\n`; print`${res}\n`;
break; break;
} }
@ -366,8 +344,6 @@ try {
printObject({ printObject({
appPath, appPath,
backupPath, backupPath,
configPath,
configExists: existsSync(configPath),
insertVersion, insertVersion,
currentVersion: manifest.version, currentVersion: manifest.version,
}); });

View File

@ -15,7 +15,7 @@ import { createRequire } from "node:module";
import patch from "./patch-desktop-app.mjs"; import patch from "./patch-desktop-app.mjs";
let __notionResources, __enhancerConfig; let __notionResources;
const nodeRequire = createRequire(import.meta.url), const nodeRequire = createRequire(import.meta.url),
manifest = nodeRequire("../package.json"), manifest = nodeRequire("../package.json"),
platform = platform =
@ -92,12 +92,6 @@ const setNotionPath = (path) => {
getAppPath = () => ["app", "app.asar"].map(getResourcePath).find(existsSync), getAppPath = () => ["app", "app.asar"].map(getResourcePath).find(existsSync),
getBackupPath = () => getBackupPath = () =>
["app.bak", "app.asar.bak"].map(getResourcePath).find(existsSync), ["app.bak", "app.asar.bak"].map(getResourcePath).find(existsSync),
getConfigPath = () => {
if (__enhancerConfig) return __enhancerConfig;
const home = platform === "wsl" ? polyfillWslEnv("HOMEPATH") : os.homedir();
__enhancerConfig = resolve(`${home}/.notion-enhancer.db`);
return __enhancerConfig;
},
checkEnhancementVersion = () => { checkEnhancementVersion = () => {
// prettier-ignore // prettier-ignore
const manifestPath = getResourcePath("app/node_modules/notion-enhancer/package.json"); const manifestPath = getResourcePath("app/node_modules/notion-enhancer/package.json");
@ -125,8 +119,8 @@ const unpackApp = async () => {
// call patch-desktop-app.mjs on each file // call patch-desktop-app.mjs on each file
// prettier-ignore // prettier-ignore
const notionScripts = (await readdirDeep(appPath)) const notionScripts = (await readdirDeep(appPath))
.filter((file) => file.endsWith(".js")), .filter((file) => file.endsWith(".js")),
scriptUpdates = []; scriptUpdates = [];
for (const file of notionScripts) { for (const file of notionScripts) {
const scriptId = file.slice(appPath.length + 1, -3).replace(/\\/g, "/"), const scriptId = file.slice(appPath.length + 1, -3).replace(/\\/g, "/"),
scriptContent = await fsp.readFile(file, { encoding: "utf8" }), scriptContent = await fsp.readFile(file, { encoding: "utf8" }),
@ -170,23 +164,16 @@ const unpackApp = async () => {
const appPath = getAppPath(); const appPath = getAppPath();
if (destPath !== appPath) await fsp.rm(appPath, { recursive: true }); if (destPath !== appPath) await fsp.rm(appPath, { recursive: true });
return true; return true;
},
removeConfig = async () => {
if (!existsSync(getConfigPath())) return;
await fsp.rm(getConfigPath());
return true;
}; };
export { export {
getResourcePath, getResourcePath,
getAppPath, getAppPath,
getBackupPath, getBackupPath,
getConfigPath,
checkEnhancementVersion, checkEnhancementVersion,
setNotionPath, setNotionPath,
unpackApp, unpackApp,
applyEnhancements, applyEnhancements,
takeBackup, takeBackup,
restoreBackup, restoreBackup,
removeConfig,
}; };

View File

@ -48,80 +48,96 @@ const sendMessage = (channel, message) => {
ipcRenderer.on(channel, listener); ipcRenderer.on(channel, listener);
}; };
let __db; let __db, __statements, __transactions;
const initDatabase = (namespace, fallbacks = {}) => { const initDatabase = (namespace, fallbacks = {}) => {
if (Array.isArray(namespace)) namespace = namespace.join("__"); if (Array.isArray(namespace)) namespace = namespace.join("__");
namespace = namespace ? namespace + "__" : ""; namespace = namespace ? namespace + "__" : "";
const namespaceify = (key) => const namespaceify = (key) =>
key.startsWith(namespace) ? key : namespace + key; key.startsWith(namespace) ? key : namespace + key;
// schema: __db ??= (async () => {
// - ("profileIds") = $profileId[] const { app, ipcRenderer } = require("electron"),
// - ("activeProfile") -> $profileId isRenderer = process?.type === "renderer",
// - $profileId: ("profileName") -> string userData = isRenderer
// - $profileId__enabledMods: ($modId) -> boolean ? await ipcRenderer.invoke("notion-enhancer", "get-user-data-folder")
// - $profileId__$modId: ($optionKey) -> value : app.getPath("userData");
const table = "settings", const table = "settings",
sqlite = require("better-sqlite3"), sqlite = require("better-sqlite3"),
db = __db ?? sqlite(path.resolve(`${os.homedir()}/.notion-enhancer.db`)), db = sqlite(path.resolve(`${userData}/notion-enhancer.db`)),
init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} ( init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} (
key TEXT PRIMARY KEY, key TEXT PRIMARY KEY,
value TEXT value TEXT
)`); )`);
init.run(); init.run();
__db = db;
const insert = db.prepare(`INSERT INTO ${table} (key, value) VALUES (?, ?)`), // schema:
update = db.prepare(`UPDATE ${table} SET value = ? WHERE key = ?`), // - ("profileIds") = $profileId[]
select = db.prepare(`SELECT * FROM ${table} WHERE key = ? LIMIT 1`), // - ("activeProfile") -> $profileId
remove = db.prepare(`DELETE FROM ${table} WHERE key = ?`), // - $profileId: ("profileName") -> string
removeMany = db.transaction((arr) => arr.forEach((key) => remove.run(key))), // - $profileId__enabledMods: ($modId) -> boolean
dump = db.prepare(`SELECT * FROM ${table}`), // - $profileId__$modId: ($optionKey) -> value
populate = db.transaction((obj) => { __statements = {
for (const key in obj) { insert: db.prepare(`INSERT INTO ${table} (key, value) VALUES (?, ?)`),
if (select.get(key)) update.run(obj[key], key); update: db.prepare(`UPDATE ${table} SET value = ? WHERE key = ?`),
else insert.run(key, obj[key]); select: db.prepare(`SELECT * FROM ${table} WHERE key = ? LIMIT 1`),
} delete: db.prepare(`DELETE FROM ${table} WHERE key = ?`),
}); dump: db.prepare(`SELECT * FROM ${table}`),
};
__transactions = {
remove: db.transaction((arr) => {
arr.forEach((key) => __statements.delete.run(key));
}),
set: db.transaction((obj) => {
for (const key in obj) {
if (__statements.select.get(key) === undefined) {
__statements.insert.run(key, obj[key]);
} else __statements.update.run(obj[key], key);
}
}),
};
return db;
})();
return { return {
// wrap methods in promises for consistency w/ chrome.storage get: async (key) => {
get: (key) => { await __db;
const fallback = fallbacks[key]; const fallback = fallbacks[key];
key = namespaceify(key); key = namespaceify(key);
try { try {
const value = JSON.parse(select.get(key)?.value); const value = JSON.parse(__statements.select.get(key)?.value);
return Promise.resolve(value ?? fallback); return value ?? fallback;
} catch {} } catch {}
return Promise.resolve(fallback); return fallback;
}, },
set: (key, value) => { set: async (key, value) => {
await __db;
key = namespaceify(key); key = namespaceify(key);
value = JSON.stringify(value); value = JSON.stringify(value);
if (select.get(key) === undefined) { __transactions.set({ [key]: value });
insert.run(key, value); return true;
} else update.run(value, key);
return Promise.resolve(true);
}, },
remove: (keys) => { remove: async (keys) => {
await __db;
keys = Array.isArray(keys) ? keys : [keys]; keys = Array.isArray(keys) ? keys : [keys];
keys = keys.map(namespaceify); keys = keys.map(namespaceify);
removeMany(keys); __transactions.remove(keys);
return Promise.resolve(true); return true;
}, },
export: () => { export: async () => {
const entries = dump await __db;
const entries = __statements.dump
.all() .all()
.filter(({ key }) => key.startsWith(namespace)) .filter(({ key }) => key.startsWith(namespace))
.map(({ key, value }) => [key.slice(namespace.length), value]); .map(({ key, value }) => [key.slice(namespace.length), value]);
return Promise.resolve(Object.fromEntries(entries)); return Object.fromEntries(entries);
}, },
import: (obj) => { import: async (obj) => {
await __db;
const entries = Object.entries(obj) // const entries = Object.entries(obj) //
.map(([key, value]) => [key.slice(namespace.length), value]); .map(([key, value]) => [key.slice(namespace.length), value]);
populate(Object.fromEntries(entries)); __transactions.set(Object.fromEntries(entries));
return Promise.resolve(true); return true;
}, },
}; };
}; };

View File

@ -21,13 +21,18 @@ if (isElectron()) {
app.exit(); app.exit();
}; };
ipcMain.on("notion-enhancer", (event, message) => { ipcMain.on("notion-enhancer", (_event, message) => {
if (message === "open-menu") { if (message === "open-menu") {
// //
} else if (message === "reload-app") { } else if (message === "reload-app") {
reloadApp(); reloadApp();
} }
}); });
ipcMain.handle("notion-enhancer", (_event, message) => {
if (message === "get-user-data-folder") {
return app.getPath("userData");
}
});
} else { } else {
const notionUrl = "https://www.notion.so/", const notionUrl = "https://www.notion.so/",
isNotionTab = (tab) => tab?.url?.startsWith(notionUrl); isNotionTab = (tab) => tab?.url?.startsWith(notionUrl);