import { objectMap, objectRemap } from '@dzeio/object-util' import type { AstroIntegration } from 'astro' import fs from 'fs/promises' interface Config { output?: string } /** * get every files recursivelly in a specific directory * * @param path the path to search * @returns the list of files recursivelly in the specific directory */ async function getFiles(path: string): Promise> { path = decodeURI(path) try { const dir = await fs.readdir(path) let files: Array = [] for (const file of dir) { if (file.startsWith('_')) { continue } const filePath = path + '/' + file if ((await fs.stat(filePath)).isDirectory()) { files = files.concat(await getFiles(filePath)) } else if (file.endsWith('.ts')) { files.push(filePath) } } return files } catch { return [] } } /** * format the path back to an url usable by the app * * @param path the path to format * @returns the path formatted as a URL */ function formatPath(basePath: string, path: string): string { // remove the base path path = path.replace(decodeURI(basePath), '') // handle the `/` endpoint if (path === '') { path = '/' } return path } async function run(config?: Config) { // get the files list const files = await getFiles('./src/pages/api').then((ev) => ev.map((it) => formatPath('./src/pages/api', it))) const methods = ['GET', 'POST', 'DELETE'] let content = '' const items: Record> = {} for (const entry of files) { const file = await fs.readFile('./src/pages/api' + entry, 'utf-8') const availableMethods: Array = [] for (const method of methods) { if (file.includes(method)) { availableMethods.push(method) } } if (availableMethods.length === 0) { continue } const prefix = entry.replace(/[/.[\]-]/g, '') content += `import type { ${availableMethods.map((it) => `${it} as ${prefix}${it}`).join((', '))} } from './pages/api${entry}'\n` let path = entry // remove the extension if asked const lastDot = path.lastIndexOf('.') path = path.slice(0, lastDot) // remove the index from the element if (path.endsWith('/index')) { path = path.replace('/index', '') } items[path] = { ...objectRemap(availableMethods, (value) => ({ key: value as string, value: `${prefix}${value as string}` })) } } content += `\ninterface APIRoutes { ${objectMap(items, (record, key) => `\t'${key}': { ${objectMap(record, (value, method) => ` ${method}: typeof ${value}`).join('\n')} }`).join('\n')} }` content += '\n\nexport default APIRoutes\n' await fs.writeFile(config?.output ?? './src/api-routes.d.ts', content) } /** * launch the integration * @returns the routng integration */ const integration = (config?: Config): AstroIntegration => ({ name: 'routing', hooks: { 'astro:build:setup': () => run(config), 'astro:server:setup': () => run(config) } }) export default integration