UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

153 lines (151 loc) 7.35 kB
import { useLogger } from "../../../logger/index.js"; import { DiffKind } from "../../../packages/types/dist/index.js"; import database_default, { isInstalled, validateDatabaseConnection } from "../../../database/index.js"; import { isNestedMetaUpdate } from "../../../utils/schema/apply-diff.js"; import { getSnapshotDiff } from "../../../utils/schema/get-snapshot-diff.js"; import { getSnapshot } from "../../../utils/schema/get-snapshot.js"; import { getLicenseManager } from "../../../license/manager.js"; import "../../../license/index.js"; import { applySnapshot } from "../../../utils/schema/apply-snapshot.js"; import path from "path"; import { parseJSON } from "@directus/utils"; import { load } from "js-yaml"; import { promises } from "fs"; import chalk from "chalk"; import inquirer from "inquirer"; //#region src/cli/commands/schema/apply.ts function filterSnapshotDiff(snapshot, filters) { const filterSet = new Set(filters); function shouldKeep(item) { if (filterSet.has(item.collection)) return false; if (item.field && filterSet.has(`${item.collection}.${item.field}`)) return false; return true; } return { collections: snapshot.collections.filter((item) => shouldKeep(item)), fields: snapshot.fields.filter((item) => shouldKeep(item)), systemFields: snapshot.systemFields.filter((item) => shouldKeep(item)), relations: snapshot.relations.filter((item) => shouldKeep(item)) }; } async function apply(snapshotPath, options) { const logger = useLogger(); const filename = path.resolve(process.cwd(), snapshotPath); const database = database_default(); await validateDatabaseConnection(database); if (await isInstalled() === false) { logger.error(`Directus isn't installed on this database. Please run "directus bootstrap" first.`); database.destroy(); process.exit(0); } let snapshot; try { const fileContents = await promises.readFile(filename, "utf8"); if (filename.endsWith(".yaml") || filename.endsWith(".yml")) snapshot = await load(fileContents); else snapshot = parseJSON(fileContents); const currentSnapshot = await getSnapshot({ database }); let snapshotDiff = getSnapshotDiff(currentSnapshot, snapshot); if (options?.ignoreRules) snapshotDiff = filterSnapshotDiff(snapshotDiff, options.ignoreRules.split(",")); if (snapshotDiff.collections.length === 0 && snapshotDiff.fields.length === 0 && snapshotDiff.systemFields.length === 0 && snapshotDiff.relations.length === 0) { logger.info("No changes to apply."); database.destroy(); process.exit(0); } const dryRun = options?.dryRun === true; const promptForChanges = !dryRun && options?.yes !== true; if (dryRun || promptForChanges) { const sections = []; if (snapshotDiff.collections.length > 0) { const lines = [chalk.underline.bold("Collections:")]; for (const { collection, diff } of snapshotDiff.collections) if (diff[0]?.kind === DiffKind.EDIT) { lines.push(` - ${chalk.magenta("Update")} ${collection}`); for (const change of diff) if (change.kind === DiffKind.EDIT) { const path$1 = formatPath(change.path); lines.push(` - Set ${path$1} to ${change.rhs}`); } } else if (diff[0]?.kind === DiffKind.DELETE) lines.push(` - ${chalk.red("Delete")} ${collection}`); else if (diff[0]?.kind === DiffKind.NEW) lines.push(` - ${chalk.green("Create")} ${collection}`); else if (diff[0]?.kind === DiffKind.ARRAY) lines.push(` - ${chalk.magenta("Update")} ${collection}`); sections.push(lines.join("\n")); } if (snapshotDiff.fields.length > 0) { const lines = [chalk.underline.bold("Fields:")]; for (const { collection, field, diff } of snapshotDiff.fields) if (diff[0]?.kind === DiffKind.EDIT || isNestedMetaUpdate(diff[0])) { lines.push(` - ${chalk.magenta("Update")} ${collection}.${field}`); for (const change of diff) { const path$1 = formatPath(change.path); if (change.kind === DiffKind.EDIT) lines.push(` - Set ${path$1} to ${change.rhs}`); else if (change.kind === DiffKind.DELETE) lines.push(` - Remove ${path$1}`); else if (change.kind === DiffKind.NEW) lines.push(` - Add ${path$1} and set it to ${change.rhs}`); } } else if (diff[0]?.kind === DiffKind.DELETE) lines.push(` - ${chalk.red("Delete")} ${collection}.${field}`); else if (diff[0]?.kind === DiffKind.NEW) lines.push(` - ${chalk.green("Create")} ${collection}.${field}`); else if (diff[0]?.kind === DiffKind.ARRAY) lines.push(` - ${chalk.magenta("Update")} ${collection}.${field}`); sections.push(lines.join("\n")); } if (snapshotDiff.systemFields.length > 0) { const lines = [chalk.underline.bold("System Fields:")]; for (const { collection, field, diff } of snapshotDiff.systemFields) if (diff[0]?.kind === DiffKind.EDIT) { lines.push(` - ${chalk.magenta("Update")} ${collection}.${field}`); for (const change of diff) { const path$1 = formatPath(change.path); if (change.kind === DiffKind.EDIT) lines.push(` - Set ${path$1} to ${change.rhs}`); else if (change.kind === DiffKind.DELETE) lines.push(` - Remove ${path$1}`); else if (change.kind === DiffKind.NEW) lines.push(` - Add ${path$1} and set it to ${change.rhs}`); } } sections.push(lines.join("\n")); } if (snapshotDiff.relations.length > 0) { const lines = [chalk.underline.bold("Relations:")]; for (const { collection, field, related_collection, diff } of snapshotDiff.relations) { const relatedCollection = formatRelatedCollection(related_collection); if (diff[0]?.kind === DiffKind.EDIT) { lines.push(` - ${chalk.magenta("Update")} ${collection}.${field}${relatedCollection}`); for (const change of diff) if (change.kind === DiffKind.EDIT) { const path$1 = formatPath(change.path); lines.push(` - Set ${path$1} to ${change.rhs}`); } } else if (diff[0]?.kind === DiffKind.DELETE) lines.push(` - ${chalk.red("Delete")} ${collection}.${field}${relatedCollection}`); else if (diff[0]?.kind === DiffKind.NEW) lines.push(` - ${chalk.green("Create")} ${collection}.${field}${relatedCollection}`); else if (diff[0]?.kind === DiffKind.ARRAY) lines.push(` - ${chalk.magenta("Update")} ${collection}.${field}${relatedCollection}`); } sections.push(lines.join("\n")); } const message = "The following changes will be applied:\n\n" + sections.join("\n\n"); if (dryRun) { console.log(message); process.exit(0); } const { proceed } = await inquirer.prompt([{ type: "confirm", name: "proceed", message: message + "\n\nWould you like to continue?" }]); if (proceed === false) process.exit(0); } await getLicenseManager().initialize(); await applySnapshot(snapshot, { current: currentSnapshot, diff: snapshotDiff, database }); logger.info(`Snapshot applied successfully`); database.destroy(); process.exit(0); } catch (err) { logger.error(err); database.destroy(); process.exit(1); } } function formatPath(path$1) { if (path$1.length === 1) return path$1.toString(); return path$1.slice(1).join("."); } function formatRelatedCollection(relatedCollection) { if (relatedCollection) return ` → ${relatedCollection}`; return ""; } //#endregion export { apply, filterSnapshotDiff, formatPath, formatRelatedCollection };