UNPKG

@sanity/migrate

Version:

Tooling for running data migrations on Sanity.io projects

103 lines (90 loc) 3.33 kB
import path from 'node:path' import {fileExists} from '@sanity/cli-core' import {getTsconfig} from 'get-tsconfig' import {isPlainObject} from 'lodash-es' import {tsImport} from 'tsx/esm/api' import {Migration} from '../../types.js' import {MIGRATION_SCRIPT_EXTENSIONS, MIGRATIONS_DIRECTORY} from './constants.js' interface ResolvedMigrationScript { /** * Absolute path to the migration script */ absolutePath: string /** * Relative path from the working directory to the migration script */ relativePath: string /** * The migration module, if it could be resolved - otherwise `undefined` */ mod?: {default: Migration; down?: unknown; up?: unknown} } /** * Resolves the potential paths to a migration script. * Considers the following paths (where `<ext>` is 'mjs', 'js', 'ts' or 'cjs'): * * - `<migrationsDir>/<migrationName>.<ext>` * - `<migrationsDir>/<migrationName>/index.<ext>` * * Note that all possible paths are returned, even if the files do not exist. * Check the `mod` property to see if a module could actually be loaded. * * @param workDir - Working directory of the studio * @param migrationName - The name of the migration directory to resolve * @returns An array of potential migration scripts * @internal */ export async function resolveMigrationScript( workDir: string, migrationName: string, ): Promise<ResolvedMigrationScript[]> { const tsconfig = getTsconfig(workDir) const locations = [migrationName, path.join(migrationName, 'index')] const results: ResolvedMigrationScript[] = [] for (const location of locations) { for (const ext of MIGRATION_SCRIPT_EXTENSIONS) { const relativePath = path.join(MIGRATIONS_DIRECTORY, `${location}.${ext}`) const absolutePath = path.resolve(workDir, relativePath) let mod: {default: Migration; down?: unknown; up?: unknown} | undefined // Check if the file exists before trying to import it const exists = await fileExists(absolutePath) if (!exists) { continue } try { const imported = await tsImport(absolutePath, { parentURL: import.meta.url, tsconfig: tsconfig?.path, }) // Handle both ESM and CJS default exports mod = imported as {default: Migration; down?: unknown; up?: unknown} } catch (err) { // Ignore MODULE_NOT_FOUND errors, but throw others if ( (err as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND' && (err as NodeJS.ErrnoException).code !== 'ERR_MODULE_NOT_FOUND' ) { throw new Error(`Error loading migration: ${(err as Error).message}`) } } results.push({absolutePath, mod, relativePath}) } } return results } /** * Checks whether or not the passed resolved migration script is actually loadable (eg has a default export) * * @param script - The resolved migration script to check * @returns `true` if the script is loadable, `false` otherwise * @internal */ export function isLoadableMigrationScript( script: ResolvedMigrationScript, ): script is Required<ResolvedMigrationScript> { if (script.mod === undefined || !isPlainObject(script.mod.default)) { return false } const mod = script.mod.default return typeof mod.title === 'string' && mod.migrate !== undefined }