Last active
February 7, 2024 16:43
-
-
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 contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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