prevent double-reading of registry, allow menu/frame scripts

This commit is contained in:
dragonwocky 2021-10-10 00:00:59 +11:00
parent 579050e349
commit 9b4b21c297
3 changed files with 206 additions and 254 deletions

View File

@ -258,34 +258,6 @@ export const create = async (
} else { } else {
path.push('content'); 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', { const json = await fs.getJSON('https://www.notion.so/api/v3/saveTransactions', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -33,107 +33,94 @@ const check = async (
} }
if (!test) { if (!test) {
if (optional && (await fmt.is(value, 'undefined'))) return true; if (optional && (await fmt.is(value, 'undefined'))) return true;
if (error) registry._errors.push({ source: mod._dir, message: error }); if (error) mod._err(error);
return false; return false;
} }
return true; return true;
}, };
validateEnvironments = (mod) => {
return check(mod, 'environments', mod.environments, 'array', { optional: true }).then( const validateEnvironments = async (mod) => {
(passed) => { mod.environments = mod.environments ?? registry.supportedEnvs;
if (!passed) return false; const isArray = await check(mod, 'environments', mod.environments, 'array');
if (!mod.environments) { if (!isArray) return false;
mod.environments = registry.supportedEnvs;
return true;
}
return mod.environments.map((tag) => return mod.environments.map((tag) =>
check(mod, 'environments.env', tag, registry.supportedEnvs) check(mod, 'environments.env', tag, registry.supportedEnvs)
); );
}
);
}, },
validateTags = (mod) => { validateTags = async (mod) => {
return check(mod, 'tags', mod.tags, 'array').then((passed) => { const isArray = await check(mod, 'tags', mod.tags, 'array');
if (!passed) return false; if (!isArray) return false;
const containsCategory = mod.tags.filter((tag) => const categoryTags = ['core', 'extension', 'theme'],
['core', 'extension', 'theme'].includes(tag) containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length;
).length;
if (!containsCategory) { if (!containsCategory) {
registry._errors.push({ mod._err(
source: mod._dir, `invalid tags (must contain at least one of 'core', 'extension', or 'theme'):
message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify( ${JSON.stringify(mod.tags)}`
mod.tags );
)}`,
});
return false; return false;
} }
if ( const isTheme = mod.tags.includes('theme'),
(mod.tags.includes('theme') && hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'),
!(mod.tags.includes('light') || mod.tags.includes('dark'))) || isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark');
(mod.tags.includes('light') && mod.tags.includes('dark')) if (isTheme && (!hasThemeMode || isBothThemeModes)) {
) { mod._err(
registry._errors.push({ `invalid tags (themes must be either 'light' or 'dark', not neither or both):
source: mod._dir, ${JSON.stringify(mod.tags)}`
message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify( );
mod.tags
)}`,
});
return false; return false;
} }
return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string'));
});
}, },
validateAuthors = (mod) => { validateAuthors = async (mod) => {
return check(mod, 'authors', mod.authors, 'array').then((passed) => { const isArray = await check(mod, 'authors', mod.authors, 'array');
if (!passed) return false; if (!isArray) return false;
return mod.authors.map((author) => [ return mod.authors.map((author) => [
check(mod, 'authors.author.name', author.name, 'string'), check(mod, 'authors.author.name', author.name, 'string'),
check(mod, 'authors.author.email', author.email, 'email', { optional: true }), check(mod, 'authors.author.email', author.email, 'email', { optional: true }),
check(mod, 'authors.author.homepage', author.homepage, 'url'), check(mod, 'authors.author.homepage', author.homepage, 'url'),
check(mod, 'authors.author.avatar', author.avatar, 'url'), check(mod, 'authors.author.avatar', author.avatar, 'url'),
]); ]);
});
}, },
validateCSS = (mod) => { validateCSS = async (mod) => {
return check(mod, 'css', mod.css, 'object').then((passed) => { const isArray = await check(mod, 'css', mod.css, 'object');
if (!passed) return false; if (!isArray) return false;
const tests = []; const tests = [];
for (let dest of ['frame', 'client', 'menu']) { for (let dest of ['frame', 'client', 'menu']) {
if (!mod.css[dest]) continue; if (!mod.css[dest]) continue;
let test = check(mod, `css.${dest}`, mod.css[dest], 'array'); let test = await check(mod, `css.${dest}`, mod.css[dest], 'array');
test = test.then((passed) => { if (test) {
if (!passed) return false; test = mod.css[dest].map((file) =>
return mod.css[dest].map((file) =>
check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' })
); );
}); }
tests.push(test); tests.push(test);
} }
return tests; return tests;
});
}, },
validateJS = (mod) => { validateJS = async (mod) => {
return check(mod, 'js', mod.js, 'object').then((passed) => { const isArray = await check(mod, 'js', mod.js, 'object');
if (!passed) return false; if (!isArray) return false;
const tests = []; const tests = [];
if (mod.js.client) { for (let dest of ['frame', 'client', 'menu']) {
let test = check(mod, 'js.client', mod.js.client, 'array'); if (!mod.js[dest]) continue;
test = test.then((passed) => { let test = await check(mod, `js.${dest}`, mod.js[dest], 'array');
if (!passed) return false; if (test) {
return mod.js.client.map((file) => test = mod.js[dest].map((file) =>
check(mod, 'js.client.file', file, 'file', { extension: '.mjs' }) check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' })
); );
}); }
tests.push(test); tests.push(test);
} }
if (mod.js.electron) { if (mod.js.electron) {
let test = check(mod, 'js.electron', mod.js.electron, 'array'); const isArray = await check(mod, 'js.electron', mod.js.electron, 'array');
test = test.then((passed) => { if (isArray) {
if (!passed) return false; for (const file of mod.js.electron) {
return mod.js.electron.map((file) => const isObject = await check(mod, 'js.electron.file', file, 'object');
check(mod, 'js.electron.file', file, 'object').then((passed) => { if (!isObject) {
if (!passed) return false; tests.push(false);
return [ continue;
}
tests.push([
check(mod, 'js.electron.file.source', file.source, 'file', { check(mod, 'js.electron.file.source', file.source, 'file', {
extension: '.mjs', extension: '.mjs',
}), }),
@ -142,83 +129,72 @@ const check = async (
check(mod, 'js.electron.file.target', file.target, 'string', { check(mod, 'js.electron.file.target', file.target, 'string', {
extension: '.js', extension: '.js',
}), }),
]; ]);
}) }
); } else tests.push(false);
});
tests.push(test);
} }
return tests; return tests;
});
}, },
validateOptions = (mod) => { validateOptions = async (mod) => {
return check(mod, 'options', mod.options, 'array').then((passed) => { const isArray = await check(mod, 'options', mod.options, 'array');
if (!passed) return false; if (!isArray) return false;
return mod.options.map((option) => const tests = [];
check(mod, 'options.option.type', option.type, registry.optionTypes).then((passed) => { for (const option of mod.options) {
if (!passed) return false; const key = 'options.option',
const tests = [ optTypeValid = await check(mod, `${key}.type`, option.type, registry.optionTypes);
check(mod, 'options.option.key', option.key, 'alphanumeric'), if (!optTypeValid) {
check(mod, 'options.option.label', option.label, 'string'), tests.push(false);
check(mod, 'options.option.tooltip', option.tooltip, 'string', { 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, optional: true,
}), }),
check(mod, 'options.option.environments', option.environments, 'array', { check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => {
optional: true, if (!isArray) return false;
}).then((passed) => {
if (!passed) return false;
if (!option.environments) {
option.environments = registry.supportedEnvs;
return true;
}
return option.environments.map((environment) => return option.environments.map((environment) =>
check( check(mod, `${key}.environments.env`, environment, registry.supportedEnvs)
mod,
'options.option.environments.env',
environment,
registry.supportedEnvs
)
); );
}), }),
]; ]);
switch (option.type) { switch (option.type) {
case 'toggle': case 'toggle':
tests.push(check(mod, 'options.option.value', option.value, 'boolean')); tests.push(check(mod, `${key}.value`, option.value, 'boolean'));
break; break;
case 'select': case 'select': {
tests.push( let test = await check(mod, `${key}.values`, option.values, 'array');
check(mod, 'options.option.values', option.values, 'array').then((passed) => { if (test) {
if (!passed) return false; test = option.values.map((value) =>
return option.values.map((value) => check(mod, `${key}.values.value`, value, 'string')
check(mod, 'options.option.values.value', value, 'string')
);
})
); );
}
tests.push(test);
break; break;
}
case 'text': case 'text':
case 'hotkey': case 'hotkey':
tests.push(check(mod, 'options.option.value', option.value, 'string')); tests.push(check(mod, `${key}.value`, option.value, 'string'));
break; break;
case 'number': case 'number':
case 'color': case 'color':
tests.push(check(mod, 'options.option.value', option.value, option.type)); tests.push(check(mod, `${key}.value`, option.value, option.type));
break; break;
case 'file': case 'file': {
tests.push( let test = await check(mod, `${key}.extensions`, option.extensions, 'array');
check(mod, 'options.option.extensions', option.extensions, 'array').then( if (test) {
(passed) => { test = option.extensions.map((ext) =>
if (!passed) return false; check(mod, `${key}.extensions.extension`, ext, 'string')
return option.extensions.map((value) =>
check(mod, 'options.option.extensions.extension', value, 'string')
); );
} }
) tests.push(test);
); break;
}
}
} }
return tests; return tests;
})
);
});
}; };
/** /**

View File

@ -14,9 +14,6 @@
import { env, fs, storage } from './_.mjs'; import { env, fs, storage } from './_.mjs';
import { validate } from './registry-validation.mjs'; import { validate } from './registry-validation.mjs';
export const _cache = [],
_errors = [];
/** /**
* mod ids whitelisted as part of the enhancer's core, permanently enabled * mod ids whitelisted as part of the enhancer's core, permanently enabled
* @constant * @constant
@ -64,27 +61,35 @@ export const welcomeNotification = {
version: env.version, version: env.version,
}; };
let _list,
_errors = [];
/** /**
* list all available mods in the repo * list all available mods in the repo
* @param {function} filter - a function to filter out mods * @param {function} filter - a function to filter out mods
* @returns {array} a validated list of mod.json objects * @returns {array} a validated list of mod.json objects
*/ */
export const list = async (filter = (mod) => true) => { export const list = async (filter = (mod) => true) => {
if (!_cache.length) { if (!_list) {
_list = new Promise(async (res, rej) => {
const passed = [];
for (const dir of await fs.getJSON('repo/registry.json')) { for (const dir of await fs.getJSON('repo/registry.json')) {
try { try {
const mod = await fs.getJSON(`repo/${dir}/mod.json`); const mod = {
mod._dir = dir; ...(await fs.getJSON(`repo/${dir}/mod.json`)),
if (await validate(mod)) _cache.push(mod); _dir: dir,
} catch (e) { _err: (message) => _errors.push({ source: dir, message }),
console.log(e); };
if (await validate(mod)) passed.push(mod);
} catch {
_errors.push({ source: dir, message: 'invalid mod.json' }); _errors.push({ source: dir, message: 'invalid mod.json' });
} }
} }
res(passed);
});
} }
const list = []; const filtered = [];
for (const mod of _cache) if (await filter(mod)) list.push(mod); for (const mod of await _list) if (await filter(mod)) filtered.push(mod);
return list; 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 * @returns {array<object>} error objects with an error message and a source directory
*/ */
export const errors = async () => { export const errors = async () => {
if (!_errors.length) await list(); await list();
return _errors; return _errors;
}; };
@ -102,8 +107,7 @@ export const errors = async () => {
* @returns {object} the mod's mod.json * @returns {object} the mod's mod.json
*/ */
export const get = async (id) => { export const get = async (id) => {
if (!_cache.length) await list(); return (await list((mod) => mod.id === id))[0];
return _cache.find((mod) => mod.id === id);
}; };
/** /**