UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 8.28 kB
{"version":3,"file":"load-external-migrations.mjs","names":["constants"],"sources":["../../../src/libs/db/load-external-migrations.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport constants from \"../../constants/constants.js\";\nimport type { Config } from \"../../types/config.js\";\nimport { LucidError } from \"../../utils/errors/index.js\";\nimport {\n\tpathExists,\n\tresolveSourcePath,\n} from \"../../utils/helpers/resolve-source-path.js\";\nimport type { ExternalMigrationFn, MigrationSource } from \"./types.js\";\n\nconst migrationFileExtensions = [\".ts\", \".mts\", \".js\", \".mjs\"];\n\nconst isMigrationFile = (fileName: string) => {\n\tif (fileName.endsWith(\".d.ts\") || fileName.endsWith(\".d.mts\")) return false;\n\treturn migrationFileExtensions.includes(path.extname(fileName));\n};\n\n//* a fixed width timestamp keeps lexicographic order in line with creation order\nconst validateMigrationName = (name: string, origin: string) => {\n\tif (!constants.db.externalMigrationNameRegex.test(name)) {\n\t\tthrow new LucidError({\n\t\t\tmessage: `Invalid migration name \"${name}\". Migration names must start with a 13 digit timestamp followed by lowercase letters, numbers, hyphens and underscores, eg. \"1751400000000-example\". Use the \"migrate:new\" command to create one.`,\n\t\t\tdata: { origin },\n\t\t});\n\t}\n};\n\n/**\n * Collects migration file paths from a file or directory source.\n */\nconst collectMigrationFiles = async (\n\tsourcePath: string,\n\toptions?: {\n\t\toptional?: boolean;\n\t},\n): Promise<string[]> => {\n\tif (!(await pathExists(sourcePath))) {\n\t\tif (options?.optional) return [];\n\t\tthrow new LucidError({\n\t\t\tmessage: `Migration source \"${sourcePath}\" does not exist.`,\n\t\t});\n\t}\n\n\tconst stats = await fs.stat(sourcePath);\n\tif (stats.isFile()) {\n\t\tif (!isMigrationFile(path.basename(sourcePath))) {\n\t\t\tthrow new LucidError({\n\t\t\t\tmessage: `Migration source \"${sourcePath}\" must be a ${migrationFileExtensions.join(\", \")} file.`,\n\t\t\t});\n\t\t}\n\t\treturn [sourcePath];\n\t}\n\tif (!stats.isDirectory()) {\n\t\tthrow new LucidError({\n\t\t\tmessage: `Migration source \"${sourcePath}\" must be a file or directory.`,\n\t\t});\n\t}\n\n\tconst entries = await fs.readdir(sourcePath, { withFileTypes: true });\n\treturn entries\n\t\t.filter((entry) => entry.isFile() && isMigrationFile(entry.name))\n\t\t.map((entry) => path.join(sourcePath, entry.name))\n\t\t.sort();\n};\n\n/**\n * Loads configured migration sources and the optional project `migrations/`\n * directory. Sources can be files, directories or inline `{ name, migration }`\n * entries. Timestamped migration names can never clash with core migration\n * names and always sort after them.\n */\nconst loadExternalMigrations = async (props: {\n\tsources?: MigrationSource[];\n\tprojectRoot?: string;\n}): Promise<Record<string, ExternalMigrationFn>> => {\n\tconst filePaths = new Set<string>();\n\tconst inlineSources: Array<{ name: string; migration: ExternalMigrationFn }> =\n\t\t[];\n\n\tfor (const source of props.sources ?? []) {\n\t\tif (typeof source === \"object\" && !(source instanceof URL)) {\n\t\t\tinlineSources.push(source);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sourcePath = await resolveSourcePath(source, {\n\t\t\tprojectRoot: props.projectRoot,\n\t\t\tlabel: \"Migration source\",\n\t\t});\n\t\tfor (const filePath of await collectMigrationFiles(sourcePath)) {\n\t\t\tfilePaths.add(filePath);\n\t\t}\n\t}\n\n\tif (props.projectRoot) {\n\t\tconst projectMigrations = await collectMigrationFiles(\n\t\t\tpath.join(props.projectRoot, constants.db.externalMigrationDirectory),\n\t\t\t{ optional: true },\n\t\t);\n\t\tfor (const filePath of projectMigrations) {\n\t\t\tfilePaths.add(filePath);\n\t\t}\n\t}\n\n\tconst migrations: Record<string, ExternalMigrationFn> = {};\n\tconst migrationOrigins: Record<string, string> = {};\n\n\tconst addMigration = (\n\t\tname: string,\n\t\tmigration: ExternalMigrationFn,\n\t\torigin: string,\n\t) => {\n\t\tif (migrationOrigins[name]) {\n\t\t\tthrow new LucidError({\n\t\t\t\tmessage: `Duplicate migration name \"${name}\". Migration names must be unique across all migration sources.`,\n\t\t\t\tdata: {\n\t\t\t\t\torigins: [migrationOrigins[name], origin],\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tmigrations[name] = migration;\n\t\tmigrationOrigins[name] = origin;\n\t};\n\n\tfor (const filePath of filePaths) {\n\t\tconst fileName = path.basename(filePath);\n\t\tconst stem = fileName.slice(\n\t\t\t0,\n\t\t\tfileName.length - path.extname(fileName).length,\n\t\t);\n\t\tvalidateMigrationName(stem, filePath);\n\n\t\t//* cache-busted so edits are picked up across config reloads (eg. dev watch mode)\n\t\tconst migrationModule: { default?: unknown } = await import(\n\t\t\t/*! @vite-ignore */\n\t\t\t`${pathToFileURL(filePath).href}?t=${Date.now()}`\n\t\t);\n\t\tif (typeof migrationModule.default !== \"function\") {\n\t\t\tthrow new LucidError({\n\t\t\t\tmessage: `Invalid migration file \"${fileName}\". Migration files must default export a migration created with the \"defineMigration\" helper.`,\n\t\t\t\tdata: { filePath },\n\t\t\t});\n\t\t}\n\n\t\taddMigration(\n\t\t\tstem,\n\t\t\tmigrationModule.default as ExternalMigrationFn,\n\t\t\tfilePath,\n\t\t);\n\t}\n\n\tfor (const source of inlineSources) {\n\t\tconst origin = `inline source \"${source.name}\"`;\n\t\tvalidateMigrationName(source.name, origin);\n\t\taddMigration(source.name, source.migration, origin);\n\t}\n\n\treturn migrations;\n};\n\n/**\n * Loads external migrations and registers them on the config's database adapter.\n * Only migration entry points should call this.\n */\nexport const prepareExternalMigrations = async (\n\tconfig: Config,\n\tprojectRoot?: string,\n) => {\n\tconfig.db.registerExternalMigrations(\n\t\tawait loadExternalMigrations({\n\t\t\tsources: config.migrations?.sources,\n\t\t\tprojectRoot,\n\t\t}),\n\t);\n};\n\nexport default loadExternalMigrations;\n"],"mappings":"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"}