template-desktop/hooks/typesafe-api.ts

118 lines
2.9 KiB
TypeScript

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<Array<string>> {
path = decodeURI(path)
try {
const dir = await fs.readdir(path)
let files: Array<string> = []
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<string, Record<string, string>> = {}
for (const entry of files) {
const file = await fs.readFile('./src/pages/api' + entry, 'utf-8')
const availableMethods: Array<string> = []
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