Skip to content

Instantly share code, notes, and snippets.

@gregfenton
Last active February 7, 2024 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregfenton/b64fe50c915e53b92edf7b20faa34b5c to your computer and use it in GitHub Desktop.
Save gregfenton/b64fe50c915e53b92edf7b20faa34b5c to your computer and use it in GitHub Desktop.
`index.ts` for my Cloud Functions project that load other CF files dynamically from the directory structure
/*
* This file is used to dynamically import and export functions from the `fs`, `http`, `sched`, and `storage` directories.
* The exported functions are then used in the `index.ts` file to define the Cloud Functions.
*
* Only files with the extension `.f.ts` are imported and exported (well, `.f.js` after `tsc` has run).
*
* Concept for this is inspired by https://codeburst.io/organizing-your-firebase-cloud-functions-67dc17b3b0da
* -- Thank you @TarikHuber !!
*/
import * as fs from 'fs';
import * as path from 'path';
let DEBUG = true;
// Object to store exported functions
const exportedFunctions: Record<string, Function> = {};
// Define a function to walk a directory recursively
const walkDirectoryAndImportFiles = async (
rootDir: string,
childPath: string
): Promise<void> => {
const absoluteDirPath = path.resolve(rootDir, childPath);
const files = fs.readdirSync(absoluteDirPath);
for (const file of files) {
const filePath = path.join(absoluteDirPath, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
const newChildPath = `${childPath}/${file}`;
await walkDirectoryAndImportFiles(rootDir, newChildPath);
} else if (stats.isFile() && file.endsWith('.f.js')) {
await importFile(rootDir, childPath, file);
}
}
};
// Define a function to import a file and dynamically export its default function
const importFile = async (
rootDir: string,
childPath: string,
file: string
): Promise<void> => {
const fullPath = path.join(rootDir, childPath, file);
try {
const module = await import(fullPath);
const defaultExport = module.default;
if (typeof defaultExport === 'function') {
const functionName = getDefaultExportName(fullPath, childPath);
if (!functionName) {
throw new Error(
`No default export function found in '${childPath}/${file}'`
);
}
exportedFunctions[functionName] = defaultExport;
DEBUG &&
console.debug(
`Exported function '${functionName}' from '${childPath}/${file}'`
);
} else {
console.log(`No default export function found in '${childPath}/${file}'`);
}
} catch (error) {
console.error(`Error importing file '${fullPath}': ${error}`);
}
};
// Define a function to get the default export name from a file
const getDefaultExportName = (fullPath: string, childPath: string): string => {
const fileContent = fs.readFileSync(fullPath, 'utf-8');
const defaultExportRegex = /export default (\w+)/;
const match = fileContent.match(defaultExportRegex);
const funcName = match ? match[1] : 'UnnamedFunction';
const dirs = childPath.split('/');
// return each of the `dirs` values where all but the first is capitalized, and append `funcName` also capitalized
const camelCaseDirs = dirs
.map((dir, idx) =>
idx === 0 ? dir : dir.charAt(0).toUpperCase() + dir.slice(1)
)
.join('');
const camelCaseFuncName =
funcName.charAt(0).toUpperCase() + funcName.slice(1);
return `${camelCaseDirs}${camelCaseFuncName}`;
};
// walk the dir tree returing a list of all directory names
const getAllDirectories = (dirPath: string): string[] => {
const directories: string[] = [];
const files = fs.readdirSync(dirPath, {withFileTypes: true});
for (const file of files) {
if (file.isDirectory()) {
directories.push(file.name);
if (file.name === 'node_modules') {
continue;
}
const subDirectories = getAllDirectories(`${dirPath}/${file.name}`);
directories.push(
...subDirectories.map((subDir) => `${file.name}/${subDir}`)
);
}
}
return directories;
};
// display list of all directory names in './'
const displayDirectories = () => {
const directories = getAllDirectories('./');
console.log(`Directories in project:`, directories);
};
/*** MAIN ***/
if (DEBUG) {
displayDirectories();
}
const rootDir = new URL('.', import.meta.url).pathname;
for (const dirName of ['fs', 'http', 'sched', 'storage']) {
await walkDirectoryAndImportFiles(rootDir, dirName);
}
// Export the dynamically imported functions
export default exportedFunctions;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment