mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-07 05:59:02 +00:00
prevent double-reading of registry, allow menu/frame scripts
This commit is contained in:
parent
579050e349
commit
9b4b21c297
@ -258,34 +258,6 @@ export const create = async (
|
||||
} else {
|
||||
path.push('content');
|
||||
}
|
||||
console.log(
|
||||
{
|
||||
pointer: {
|
||||
table: parentTable,
|
||||
id: parentID,
|
||||
spaceId: spaceID,
|
||||
},
|
||||
path,
|
||||
command: prepend ? 'listBefore' : 'listAfter',
|
||||
args: {
|
||||
...(siblingID ? { after: siblingID } : {}),
|
||||
id: recordID,
|
||||
},
|
||||
},
|
||||
{
|
||||
pointer: {
|
||||
table: recordTable,
|
||||
id: recordID,
|
||||
spaceId: spaceID,
|
||||
},
|
||||
path: [],
|
||||
command: 'set',
|
||||
args: {
|
||||
...args,
|
||||
...recordValue,
|
||||
},
|
||||
}
|
||||
);
|
||||
const json = await fs.getJSON('https://www.notion.so/api/v3/saveTransactions', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -9,216 +9,192 @@
|
||||
import { fmt, registry } from './_.mjs';
|
||||
|
||||
const check = async (
|
||||
mod,
|
||||
key,
|
||||
value,
|
||||
types,
|
||||
{
|
||||
extension = '',
|
||||
error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify(
|
||||
value
|
||||
)}`,
|
||||
optional = false,
|
||||
} = {}
|
||||
) => {
|
||||
let test;
|
||||
for (const type of Array.isArray(types) ? [types] : types.split('|')) {
|
||||
if (type === 'file') {
|
||||
test =
|
||||
value && !value.startsWith('http')
|
||||
? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension })
|
||||
: false;
|
||||
} else test = await fmt.is(value, type, { extension });
|
||||
if (test) break;
|
||||
}
|
||||
if (!test) {
|
||||
if (optional && (await fmt.is(value, 'undefined'))) return true;
|
||||
if (error) registry._errors.push({ source: mod._dir, message: error });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validateEnvironments = (mod) => {
|
||||
return check(mod, 'environments', mod.environments, 'array', { optional: true }).then(
|
||||
(passed) => {
|
||||
if (!passed) return false;
|
||||
if (!mod.environments) {
|
||||
mod.environments = registry.supportedEnvs;
|
||||
return true;
|
||||
}
|
||||
return mod.environments.map((tag) =>
|
||||
check(mod, 'environments.env', tag, registry.supportedEnvs)
|
||||
);
|
||||
}
|
||||
mod,
|
||||
key,
|
||||
value,
|
||||
types,
|
||||
{
|
||||
extension = '',
|
||||
error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify(
|
||||
value
|
||||
)}`,
|
||||
optional = false,
|
||||
} = {}
|
||||
) => {
|
||||
let test;
|
||||
for (const type of Array.isArray(types) ? [types] : types.split('|')) {
|
||||
if (type === 'file') {
|
||||
test =
|
||||
value && !value.startsWith('http')
|
||||
? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension })
|
||||
: false;
|
||||
} else test = await fmt.is(value, type, { extension });
|
||||
if (test) break;
|
||||
}
|
||||
if (!test) {
|
||||
if (optional && (await fmt.is(value, 'undefined'))) return true;
|
||||
if (error) mod._err(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateEnvironments = async (mod) => {
|
||||
mod.environments = mod.environments ?? registry.supportedEnvs;
|
||||
const isArray = await check(mod, 'environments', mod.environments, 'array');
|
||||
if (!isArray) return false;
|
||||
return mod.environments.map((tag) =>
|
||||
check(mod, 'environments.env', tag, registry.supportedEnvs)
|
||||
);
|
||||
},
|
||||
validateTags = (mod) => {
|
||||
return check(mod, 'tags', mod.tags, 'array').then((passed) => {
|
||||
if (!passed) return false;
|
||||
const containsCategory = mod.tags.filter((tag) =>
|
||||
['core', 'extension', 'theme'].includes(tag)
|
||||
).length;
|
||||
if (!containsCategory) {
|
||||
registry._errors.push({
|
||||
source: mod._dir,
|
||||
message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify(
|
||||
mod.tags
|
||||
)}`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
(mod.tags.includes('theme') &&
|
||||
!(mod.tags.includes('light') || mod.tags.includes('dark'))) ||
|
||||
(mod.tags.includes('light') && mod.tags.includes('dark'))
|
||||
) {
|
||||
registry._errors.push({
|
||||
source: mod._dir,
|
||||
message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify(
|
||||
mod.tags
|
||||
)}`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string'));
|
||||
});
|
||||
},
|
||||
validateAuthors = (mod) => {
|
||||
return check(mod, 'authors', mod.authors, 'array').then((passed) => {
|
||||
if (!passed) return false;
|
||||
return mod.authors.map((author) => [
|
||||
check(mod, 'authors.author.name', author.name, 'string'),
|
||||
check(mod, 'authors.author.email', author.email, 'email', { optional: true }),
|
||||
check(mod, 'authors.author.homepage', author.homepage, 'url'),
|
||||
check(mod, 'authors.author.avatar', author.avatar, 'url'),
|
||||
]);
|
||||
});
|
||||
},
|
||||
validateCSS = (mod) => {
|
||||
return check(mod, 'css', mod.css, 'object').then((passed) => {
|
||||
if (!passed) return false;
|
||||
const tests = [];
|
||||
for (let dest of ['frame', 'client', 'menu']) {
|
||||
if (!mod.css[dest]) continue;
|
||||
let test = check(mod, `css.${dest}`, mod.css[dest], 'array');
|
||||
test = test.then((passed) => {
|
||||
if (!passed) return false;
|
||||
return mod.css[dest].map((file) =>
|
||||
check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' })
|
||||
);
|
||||
});
|
||||
tests.push(test);
|
||||
}
|
||||
return tests;
|
||||
});
|
||||
},
|
||||
validateJS = (mod) => {
|
||||
return check(mod, 'js', mod.js, 'object').then((passed) => {
|
||||
if (!passed) return false;
|
||||
const tests = [];
|
||||
if (mod.js.client) {
|
||||
let test = check(mod, 'js.client', mod.js.client, 'array');
|
||||
test = test.then((passed) => {
|
||||
if (!passed) return false;
|
||||
return mod.js.client.map((file) =>
|
||||
check(mod, 'js.client.file', file, 'file', { extension: '.mjs' })
|
||||
);
|
||||
});
|
||||
tests.push(test);
|
||||
}
|
||||
if (mod.js.electron) {
|
||||
let test = check(mod, 'js.electron', mod.js.electron, 'array');
|
||||
test = test.then((passed) => {
|
||||
if (!passed) return false;
|
||||
return mod.js.electron.map((file) =>
|
||||
check(mod, 'js.electron.file', file, 'object').then((passed) => {
|
||||
if (!passed) return false;
|
||||
return [
|
||||
check(mod, 'js.electron.file.source', file.source, 'file', {
|
||||
extension: '.mjs',
|
||||
}),
|
||||
// referencing the file within the electron app
|
||||
// existence can't be validated, so only format is
|
||||
check(mod, 'js.electron.file.target', file.target, 'string', {
|
||||
extension: '.js',
|
||||
}),
|
||||
];
|
||||
})
|
||||
);
|
||||
});
|
||||
tests.push(test);
|
||||
}
|
||||
return tests;
|
||||
});
|
||||
},
|
||||
validateOptions = (mod) => {
|
||||
return check(mod, 'options', mod.options, 'array').then((passed) => {
|
||||
if (!passed) return false;
|
||||
return mod.options.map((option) =>
|
||||
check(mod, 'options.option.type', option.type, registry.optionTypes).then((passed) => {
|
||||
if (!passed) return false;
|
||||
const tests = [
|
||||
check(mod, 'options.option.key', option.key, 'alphanumeric'),
|
||||
check(mod, 'options.option.label', option.label, 'string'),
|
||||
check(mod, 'options.option.tooltip', option.tooltip, 'string', {
|
||||
optional: true,
|
||||
}),
|
||||
check(mod, 'options.option.environments', option.environments, 'array', {
|
||||
optional: true,
|
||||
}).then((passed) => {
|
||||
if (!passed) return false;
|
||||
if (!option.environments) {
|
||||
option.environments = registry.supportedEnvs;
|
||||
return true;
|
||||
}
|
||||
return option.environments.map((environment) =>
|
||||
check(
|
||||
mod,
|
||||
'options.option.environments.env',
|
||||
environment,
|
||||
registry.supportedEnvs
|
||||
)
|
||||
);
|
||||
}),
|
||||
];
|
||||
switch (option.type) {
|
||||
case 'toggle':
|
||||
tests.push(check(mod, 'options.option.value', option.value, 'boolean'));
|
||||
break;
|
||||
case 'select':
|
||||
tests.push(
|
||||
check(mod, 'options.option.values', option.values, 'array').then((passed) => {
|
||||
if (!passed) return false;
|
||||
return option.values.map((value) =>
|
||||
check(mod, 'options.option.values.value', value, 'string')
|
||||
);
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'text':
|
||||
case 'hotkey':
|
||||
tests.push(check(mod, 'options.option.value', option.value, 'string'));
|
||||
break;
|
||||
case 'number':
|
||||
case 'color':
|
||||
tests.push(check(mod, 'options.option.value', option.value, option.type));
|
||||
break;
|
||||
case 'file':
|
||||
tests.push(
|
||||
check(mod, 'options.option.extensions', option.extensions, 'array').then(
|
||||
(passed) => {
|
||||
if (!passed) return false;
|
||||
return option.extensions.map((value) =>
|
||||
check(mod, 'options.option.extensions.extension', value, 'string')
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return tests;
|
||||
})
|
||||
validateTags = async (mod) => {
|
||||
const isArray = await check(mod, 'tags', mod.tags, 'array');
|
||||
if (!isArray) return false;
|
||||
const categoryTags = ['core', 'extension', 'theme'],
|
||||
containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length;
|
||||
if (!containsCategory) {
|
||||
mod._err(
|
||||
`invalid tags (must contain at least one of 'core', 'extension', or 'theme'):
|
||||
${JSON.stringify(mod.tags)}`
|
||||
);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const isTheme = mod.tags.includes('theme'),
|
||||
hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'),
|
||||
isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark');
|
||||
if (isTheme && (!hasThemeMode || isBothThemeModes)) {
|
||||
mod._err(
|
||||
`invalid tags (themes must be either 'light' or 'dark', not neither or both):
|
||||
${JSON.stringify(mod.tags)}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string'));
|
||||
},
|
||||
validateAuthors = async (mod) => {
|
||||
const isArray = await check(mod, 'authors', mod.authors, 'array');
|
||||
if (!isArray) return false;
|
||||
return mod.authors.map((author) => [
|
||||
check(mod, 'authors.author.name', author.name, 'string'),
|
||||
check(mod, 'authors.author.email', author.email, 'email', { optional: true }),
|
||||
check(mod, 'authors.author.homepage', author.homepage, 'url'),
|
||||
check(mod, 'authors.author.avatar', author.avatar, 'url'),
|
||||
]);
|
||||
},
|
||||
validateCSS = async (mod) => {
|
||||
const isArray = await check(mod, 'css', mod.css, 'object');
|
||||
if (!isArray) return false;
|
||||
const tests = [];
|
||||
for (let dest of ['frame', 'client', 'menu']) {
|
||||
if (!mod.css[dest]) continue;
|
||||
let test = await check(mod, `css.${dest}`, mod.css[dest], 'array');
|
||||
if (test) {
|
||||
test = mod.css[dest].map((file) =>
|
||||
check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' })
|
||||
);
|
||||
}
|
||||
tests.push(test);
|
||||
}
|
||||
return tests;
|
||||
},
|
||||
validateJS = async (mod) => {
|
||||
const isArray = await check(mod, 'js', mod.js, 'object');
|
||||
if (!isArray) return false;
|
||||
const tests = [];
|
||||
for (let dest of ['frame', 'client', 'menu']) {
|
||||
if (!mod.js[dest]) continue;
|
||||
let test = await check(mod, `js.${dest}`, mod.js[dest], 'array');
|
||||
if (test) {
|
||||
test = mod.js[dest].map((file) =>
|
||||
check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' })
|
||||
);
|
||||
}
|
||||
tests.push(test);
|
||||
}
|
||||
if (mod.js.electron) {
|
||||
const isArray = await check(mod, 'js.electron', mod.js.electron, 'array');
|
||||
if (isArray) {
|
||||
for (const file of mod.js.electron) {
|
||||
const isObject = await check(mod, 'js.electron.file', file, 'object');
|
||||
if (!isObject) {
|
||||
tests.push(false);
|
||||
continue;
|
||||
}
|
||||
tests.push([
|
||||
check(mod, 'js.electron.file.source', file.source, 'file', {
|
||||
extension: '.mjs',
|
||||
}),
|
||||
// referencing the file within the electron app
|
||||
// existence can't be validated, so only format is
|
||||
check(mod, 'js.electron.file.target', file.target, 'string', {
|
||||
extension: '.js',
|
||||
}),
|
||||
]);
|
||||
}
|
||||
} else tests.push(false);
|
||||
}
|
||||
return tests;
|
||||
},
|
||||
validateOptions = async (mod) => {
|
||||
const isArray = await check(mod, 'options', mod.options, 'array');
|
||||
if (!isArray) return false;
|
||||
const tests = [];
|
||||
for (const option of mod.options) {
|
||||
const key = 'options.option',
|
||||
optTypeValid = await check(mod, `${key}.type`, option.type, registry.optionTypes);
|
||||
if (!optTypeValid) {
|
||||
tests.push(false);
|
||||
continue;
|
||||
}
|
||||
option.environments = option.environments ?? registry.supportedEnvs;
|
||||
tests.push([
|
||||
check(mod, `${key}.key`, option.key, 'alphanumeric'),
|
||||
check(mod, `${key}.label`, option.label, 'string'),
|
||||
check(mod, `${key}.tooltip`, option.tooltip, 'string', {
|
||||
optional: true,
|
||||
}),
|
||||
check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => {
|
||||
if (!isArray) return false;
|
||||
return option.environments.map((environment) =>
|
||||
check(mod, `${key}.environments.env`, environment, registry.supportedEnvs)
|
||||
);
|
||||
}),
|
||||
]);
|
||||
switch (option.type) {
|
||||
case 'toggle':
|
||||
tests.push(check(mod, `${key}.value`, option.value, 'boolean'));
|
||||
break;
|
||||
case 'select': {
|
||||
let test = await check(mod, `${key}.values`, option.values, 'array');
|
||||
if (test) {
|
||||
test = option.values.map((value) =>
|
||||
check(mod, `${key}.values.value`, value, 'string')
|
||||
);
|
||||
}
|
||||
tests.push(test);
|
||||
break;
|
||||
}
|
||||
case 'text':
|
||||
case 'hotkey':
|
||||
tests.push(check(mod, `${key}.value`, option.value, 'string'));
|
||||
break;
|
||||
case 'number':
|
||||
case 'color':
|
||||
tests.push(check(mod, `${key}.value`, option.value, option.type));
|
||||
break;
|
||||
case 'file': {
|
||||
let test = await check(mod, `${key}.extensions`, option.extensions, 'array');
|
||||
if (test) {
|
||||
test = option.extensions.map((ext) =>
|
||||
check(mod, `${key}.extensions.extension`, ext, 'string')
|
||||
);
|
||||
}
|
||||
tests.push(test);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tests;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -14,9 +14,6 @@
|
||||
import { env, fs, storage } from './_.mjs';
|
||||
import { validate } from './registry-validation.mjs';
|
||||
|
||||
export const _cache = [],
|
||||
_errors = [];
|
||||
|
||||
/**
|
||||
* mod ids whitelisted as part of the enhancer's core, permanently enabled
|
||||
* @constant
|
||||
@ -64,27 +61,35 @@ export const welcomeNotification = {
|
||||
version: env.version,
|
||||
};
|
||||
|
||||
let _list,
|
||||
_errors = [];
|
||||
/**
|
||||
* list all available mods in the repo
|
||||
* @param {function} filter - a function to filter out mods
|
||||
* @returns {array} a validated list of mod.json objects
|
||||
*/
|
||||
export const list = async (filter = (mod) => true) => {
|
||||
if (!_cache.length) {
|
||||
for (const dir of await fs.getJSON('repo/registry.json')) {
|
||||
try {
|
||||
const mod = await fs.getJSON(`repo/${dir}/mod.json`);
|
||||
mod._dir = dir;
|
||||
if (await validate(mod)) _cache.push(mod);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
_errors.push({ source: dir, message: 'invalid mod.json' });
|
||||
if (!_list) {
|
||||
_list = new Promise(async (res, rej) => {
|
||||
const passed = [];
|
||||
for (const dir of await fs.getJSON('repo/registry.json')) {
|
||||
try {
|
||||
const mod = {
|
||||
...(await fs.getJSON(`repo/${dir}/mod.json`)),
|
||||
_dir: dir,
|
||||
_err: (message) => _errors.push({ source: dir, message }),
|
||||
};
|
||||
if (await validate(mod)) passed.push(mod);
|
||||
} catch {
|
||||
_errors.push({ source: dir, message: 'invalid mod.json' });
|
||||
}
|
||||
}
|
||||
}
|
||||
res(passed);
|
||||
});
|
||||
}
|
||||
const list = [];
|
||||
for (const mod of _cache) if (await filter(mod)) list.push(mod);
|
||||
return list;
|
||||
const filtered = [];
|
||||
for (const mod of await _list) if (await filter(mod)) filtered.push(mod);
|
||||
return filtered;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -92,7 +97,7 @@ export const list = async (filter = (mod) => true) => {
|
||||
* @returns {array<object>} error objects with an error message and a source directory
|
||||
*/
|
||||
export const errors = async () => {
|
||||
if (!_errors.length) await list();
|
||||
await list();
|
||||
return _errors;
|
||||
};
|
||||
|
||||
@ -102,8 +107,7 @@ export const errors = async () => {
|
||||
* @returns {object} the mod's mod.json
|
||||
*/
|
||||
export const get = async (id) => {
|
||||
if (!_cache.length) await list();
|
||||
return _cache.find((mod) => mod.id === id);
|
||||
return (await list((mod) => mod.id === id))[0];
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user