Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
feat: update globImport API to better work with Deno security policies
Browse files Browse the repository at this point in the history
  • Loading branch information
pskfyi committed Dec 18, 2022
1 parent 3c4eb57 commit 06b0cc0
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 33 deletions.
53 changes: 33 additions & 20 deletions fs/globImport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,33 @@ const globPattern = resolve(ROOT_DIR, "fixture", "**", "*.ts");
const A_DIR = resolve(ROOT_DIR, "fixture", "a");
const C_DIR = resolve(A_DIR, "b", "c");

const A_FILE = resolve(A_DIR, "findme.ts");
const C_FILE = resolve(C_DIR, "findme.ts");
const A_TS = resolve(A_DIR, "findme.ts");
const C_TS = resolve(C_DIR, "findme.ts");

const stubFileHandler = () => async () => await 1;

const importHandler = (filePath: string) => () => import(filePath);

Deno.test("fs.globImport", async (t) => {
await t.step(
"finds files that match the glob pattern",
async () => {
const importMap = await globImport(globPattern);
const importMap = await globImport(
globPattern,
stubFileHandler,
);

assertEquals(
Object.keys(importMap),
[A_FILE, C_FILE],
[A_TS, C_TS],
);
},
);

await t.step(
"is lazy by default, not calling its import functions",
async () => {
const importMap = await globImport(globPattern);
const importMap = await globImport(globPattern, stubFileHandler);

assert(
Object.values(importMap).every((val) => typeof val === "function"),
Expand All @@ -39,40 +46,46 @@ Deno.test("fs.globImport", async (t) => {
await t.step(
"options.eager causes all imports to be resolved",
async () => {
const importMap = await globImport(globPattern, { eager: true });
const importMap = await globImport(
globPattern,
importHandler,
{ eager: true },
);

assertEquals(
importMap,
{
[A_FILE]: { name: "A" },
[C_FILE]: { name: "C" },
[A_TS]: { name: "A" },
[C_TS]: { name: "C" },
},
);
},
);

await t.step(
"options.eager causes all imports to be resolved",
"fileHandler accepts a map from extensions to specific handlers",
async () => {
const globPattern = resolve(ROOT_DIR, "fixture", "**", "*.md");
const globPattern = resolve(ROOT_DIR, "fixture", "**", "*.*");

const options = {
eager: true,
fileHandlers: {
const importMap = await globImport(
globPattern,
{
".md": (filePath: string) => () => Deno.readTextFile(filePath),
".ts": importHandler,
},
};

const importMap = await globImport(globPattern, options);
{ eager: true },
);

const A = resolve(A_DIR, "findme.md");
const C = resolve(C_DIR, "findme.md");
const A_MD = resolve(A_DIR, "findme.md");
const C_MD = resolve(C_DIR, "findme.md");

assertEquals(
importMap,
{
[A]: "A\n",
[C]: "C\n",
[A_MD]: "A\n",
[A_TS]: { name: "A" },
[C_MD]: "C\n",
[C_TS]: { name: "C" },
},
);
},
Expand Down
46 changes: 33 additions & 13 deletions fs/globImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ export type EagerImportMap = Record<string, any>;
// deno-lint-ignore no-explicit-any
export type ImportFactory = (filePath: string) => () => Promise<any>;

export type FileHandlers = {
export type FileHandler = ImportFactory | {
[Extension: string]: ImportFactory;
};

export const DEFAULT_IMPORT_FACTORY: ImportFactory = (filePath: string) => () =>
import(filePath);

export type GlobImportOptions = {
/**
* When true, import functions will be called and the results will be
* returned.
*/
eager?: boolean;
fileHandlers?: FileHandlers;
/**
* When provided alongside an object `FileHandler`, if a file extension is
* not found in the object, this fallback will be used rather than throwing.
*/
fallbackHandler?: ImportFactory;
};

/**
Expand Down Expand Up @@ -57,33 +58,52 @@ export type GlobImportOptions = {
*/
export async function globImport(
globPattern: string,
options?: { eager?: false; fileHandlers?: FileHandlers },
fileHandler: FileHandler,
options?: { eager?: false },
): Promise<ImportMap>;
export async function globImport(
globPattern: string,
options: { eager: true; fileHandlers?: FileHandlers },
fileHandler: FileHandler,
options: { eager: true },
): Promise<EagerImportMap>;
export async function globImport(
globPattern: string,
fileHandler: FileHandler,
options: GlobImportOptions,
): Promise<ImportMap | EagerImportMap>;
export async function globImport(
globPattern: string,
fileHandler: FileHandler,
options: GlobImportOptions = {},
) {
const { eager = false, fileHandlers } = options;
const handlers = { ...fileHandlers };
const { eager = false, fallbackHandler } = options;
const filePaths = await glob(globPattern);

function _getHandler(extension: string): ImportFactory {
if (typeof fileHandler === "function") return fileHandler;

if (extension in fileHandler) return fileHandler[extension];

if (fallbackHandler) return fallbackHandler;

const extensions = Object.keys(fileHandler);

throw new Error(
"Could not find a file handler for the following extension, and no fallback handler was registered.\n" +
`extension found: ${extension}\n` +
`registered extensions: ${JSON.stringify(extensions)}`,
);
}

const entries = await Promise.all(
filePaths.map(async (filePath) => {
const ext = extname(filePath);
const extension = extname(filePath);

if (!(ext in handlers)) handlers[ext] = DEFAULT_IMPORT_FACTORY;
const handler = _getHandler(extension);

const fn = handlers[ext](filePath);
const importFunction = handler(filePath);

return [filePath, eager ? await fn() : fn];
return [filePath, eager ? await importFunction() : importFunction];
}),
);

Expand Down

0 comments on commit 06b0cc0

Please sign in to comment.