@overlook/plugin-fs
Version:
Overlook framework file system plugin
146 lines (125 loc) • 3.96 kB
JavaScript
/* --------------------
* @overlook/plugin-fs module
* Entry point
* ------------------*/
;
// Modules
// eslint-disable-next-line node/no-unsupported-features/node-builtins
const {readFile} = require('fs').promises,
Plugin = require('@overlook/plugin'),
{INIT_PROPS, INIT_ROUTE} = require('@overlook/route'),
{findParent} = require('@overlook/util-find-parent'),
assert = require('simple-invariant'),
{isString, isFullString, isObject} = require('is-it-type');
// Imports
const pkg = require('../package.json');
// Constants
const VIRTUAL_PATH_PREFIX = '?/';
// Exports
class File {
constructor(path, content) {
// Validate input
assert(path == null || isFullString(path), 'File path must be a string if provided');
assert(content == null || isString(content), 'File content must be a string if provided');
assert(path || content, 'File must have either path or content defined');
if (path == null) path = undefined;
if (content == null) content = undefined;
// Save props
this.path = path;
this.content = content;
}
}
const fsPlugin = new Plugin(
pkg,
{
symbols: [
'GET_FILE_PATH', 'READ_FILE', 'WRITE_FILE', 'CREATE_VIRTUAL_PATH', 'FS_FILES'
]
},
(Route, {
GET_FILE_PATH, READ_FILE, WRITE_FILE, CREATE_VIRTUAL_PATH, FS_FILES
}) => class FsRoute extends Route {
[INIT_PROPS]() {
this[FS_FILES] = undefined;
}
async [INIT_ROUTE]() {
await super[INIT_ROUTE]();
// Init `[FS_FILES]` - children inherit it from FS root
const parent = findParent(this, route => route[FS_FILES]);
if (parent) {
this[FS_FILES] = parent[FS_FILES];
} else {
this[FS_FILES] = Object.create(null);
}
}
/**
* Get path of file.
* Method can be extended in plugins.
* @param {Object} file - File object
* @returns {string} - File path
*/
[GET_FILE_PATH](file) { // eslint-disable-line class-methods-use-this
// Validate input
const path = file && file.path;
assert(isObject(file) && isFullString(path), 'file must be a File object with a path');
assert(
!path.startsWith(VIRTUAL_PATH_PREFIX),
'Virtual file paths cannot be resolved to absolute paths'
);
// Return path
return path;
}
/**
* Read content of a file, either from real or virtual file system.
* @param {Object} file - File object
* @returns {string} - File content
*/
async [READ_FILE](file) {
// Validate input
assert(isObject(file), 'file must be a File object');
// Return content if defined
if (file.content !== undefined) return file.content;
// Read from filesystem
const path = this[GET_FILE_PATH](file);
return await readFile(path, 'utf8'); // eslint-disable-line no-return-await
}
/**
* Write a file to virtual file system.
* File will be given a pseudo-path.
* @param {string} ext - File extension
* @param {string} content - File content
* @returns {Object} - File object
*/
async [WRITE_FILE](ext, content) {
// Validate input
assert(isFullString(ext), 'ext must be a non-empty string');
assert(isString(content), 'content must be a string');
// Create File with pseudo-path
const path = this[CREATE_VIRTUAL_PATH](ext);
const file = new File(path, content);
this[FS_FILES][path] = file;
return file;
}
/**
* Create a pseudo-path for virtual file.
* Path will be of form `?/index.js`.
* Paths are guaranteed to be unique.
* Method can be extended in plugins.
* @param {string} ext - File extension
* @returns {string} - Pseudo-path
*/
[CREATE_VIRTUAL_PATH](ext) {
const name = this.name || 'anon',
files = this[FS_FILES];
let path;
for (let i = 0; true; i++) { // eslint-disable-line no-constant-condition
path = `${VIRTUAL_PATH_PREFIX}${name}${i > 0 ? i : ''}.${ext}`;
if (!files[path]) break;
}
return path;
}
}
);
fsPlugin.File = File;
fsPlugin.VIRTUAL_PATH_PREFIX = VIRTUAL_PATH_PREFIX;
module.exports = fsPlugin;