{"version":3,"file":"apis.mjs","sources":["../../../src/core/loaders/apis.ts"],"sourcesContent":["import { join, extname, basename } from 'path';\nimport fse, { existsSync } from 'fs-extra';\nimport _ from 'lodash';\nimport { isKebabCase, importDefault } from '@strapi/utils';\nimport { isEmpty } from 'lodash/fp';\nimport type { Strapi, Common, Schema } from '@strapi/types';\n\ninterface API {\n  bootstrap: () => void | Promise<void>;\n  destroy: () => void | Promise<void>;\n  register: () => void | Promise<void>;\n  config: Record<string, unknown>;\n  routes: Record<string, Common.Router>;\n  controllers: Record<string, Common.Controller>;\n  services: Record<string, Common.Service>;\n  policies: Record<string, Common.Policy>;\n  middlewares: Record<string, Common.Middleware>;\n  contentTypes: Record<string, { schema: Schema.ContentType }>;\n}\n\ninterface APIs {\n  [key: string]: API;\n}\n\nconst DEFAULT_CONTENT_TYPE = {\n  schema: {},\n  actions: {},\n  lifecycles: {},\n};\n\n// to handle names with numbers in it we first check if it is already in kebabCase\nconst normalizeName = (name: string) => (isKebabCase(name) ? name : _.kebabCase(name));\n\nconst isDirectory = (fd: fse.Dirent) => fd.isDirectory();\nconst isDotFile = (fd: fse.Dirent) => fd.name.startsWith('.');\n\nexport default async function loadAPIs(strapi: Strapi) {\n  if (!existsSync(strapi.dirs.dist.api)) {\n    return;\n  }\n\n  const apisFDs = await (await fse.readdir(strapi.dirs.dist.api, { withFileTypes: true }))\n    .filter(isDirectory)\n    .filter(_.negate(isDotFile));\n\n  const apis: APIs = {};\n\n  // only load folders\n  for (const apiFD of apisFDs) {\n    const apiName = normalizeName(apiFD.name);\n    const api = await loadAPI(join(strapi.dirs.dist.api, apiFD.name));\n\n    apis[apiName] = api;\n  }\n\n  validateContentTypesUnicity(apis);\n\n  for (const apiName of Object.keys(apis)) {\n    strapi.container.get('apis').add(apiName, apis[apiName]);\n  }\n}\n\nconst validateContentTypesUnicity = (apis: APIs) => {\n  const allApisSchemas = Object.values(apis).flatMap((api) => Object.values(api.contentTypes));\n\n  const names: string[] = [];\n  allApisSchemas.forEach(({ schema }) => {\n    if (schema.info.singularName) {\n      const singularName = _.kebabCase(schema.info.singularName);\n      if (names.includes(singularName)) {\n        throw new Error(`The singular name \"${schema.info.singularName}\" should be unique`);\n      }\n      names.push(singularName);\n    }\n\n    if (schema.info.pluralName) {\n      const pluralName = _.kebabCase(schema.info.pluralName);\n      if (names.includes(pluralName)) {\n        throw new Error(`The plural name \"${schema.info.pluralName}\" should be unique`);\n      }\n      names.push(pluralName);\n    }\n  });\n};\n\nconst loadAPI = async (dir: string) => {\n  const [index, config, routes, controllers, services, policies, middlewares, contentTypes] =\n    await Promise.all([\n      loadIndex(dir),\n      loadDir(join(dir, 'config')),\n      loadDir(join(dir, 'routes')),\n      loadDir(join(dir, 'controllers')),\n      loadDir(join(dir, 'services')),\n      loadDir(join(dir, 'policies')),\n      loadDir(join(dir, 'middlewares')),\n      loadContentTypes(join(dir, 'content-types')),\n    ]);\n\n  return {\n    ...(index || {}),\n    config: config || {},\n    routes: routes || [],\n    controllers: controllers || {},\n    services: services || {},\n    policies: policies || {},\n    middlewares: middlewares || {},\n    contentTypes: contentTypes || {},\n  };\n};\n\nconst loadIndex = async (dir: string) => {\n  if (await fse.pathExists(join(dir, 'index.js'))) {\n    return loadFile(join(dir, 'index.js'));\n  }\n};\n\nconst loadContentTypes = async (dir: string) => {\n  if (!(await fse.pathExists(dir))) {\n    return;\n  }\n\n  const fds = await fse.readdir(dir, { withFileTypes: true });\n  const contentTypes: API['contentTypes'] = {};\n\n  // only load folders\n  for (const fd of fds) {\n    if (fd.isFile()) {\n      continue;\n    }\n\n    const contentTypeName = normalizeName(fd.name);\n    const contentType = await loadDir(join(dir, fd.name));\n\n    if (isEmpty(contentType) || isEmpty(contentType.schema)) {\n      throw new Error(`Could not load content type found at ${dir}`);\n    }\n\n    contentTypes[normalizeName(contentTypeName)] = _.defaults(\n      contentType as { schema: Schema.ContentType },\n      DEFAULT_CONTENT_TYPE\n    );\n  }\n\n  return contentTypes;\n};\n\nconst loadDir = async (dir: string) => {\n  if (!(await fse.pathExists(dir))) {\n    return;\n  }\n\n  const fds = await fse.readdir(dir, { withFileTypes: true });\n\n  const root: Record<string, unknown> = {};\n  for (const fd of fds) {\n    if (!fd.isFile() || extname(fd.name) === '.map') {\n      continue;\n    }\n\n    const key = basename(fd.name, extname(fd.name));\n\n    root[normalizeName(key)] = await loadFile(join(dir, fd.name));\n  }\n\n  return root;\n};\n\nconst loadFile = (file: string) => {\n  const ext = extname(file);\n\n  switch (ext) {\n    case '.js':\n      return importDefault(file);\n    case '.json':\n      return fse.readJSON(file);\n    default:\n      return {};\n  }\n};\n"],"names":[],"mappings":"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"}