UNPKG

@yeoman/conflicter

Version:

Conflict resolution for yeoman's generator/environment stack

130 lines (129 loc) 5.34 kB
/// <reference types="node" resolution-mode="require"/> /// <reference types="node" resolution-mode="require"/> import { Buffer } from 'node:buffer'; import type { InputOutputAdapter } from '@yeoman/adapter/types'; import type expand from '@inquirer/expand'; import type { Separator } from '@inquirer/expand'; import { type Change, DiffLinesOptionsNonabortable, DiffWordsOptionsNonabortable } from 'diff'; import { type FileTransform } from 'mem-fs'; import type { MemFsEditorFile } from 'mem-fs-editor'; declare const statusToSkipFile: readonly ["skip", "diff", "ignore"]; export type ConflicterLog = ['create', 'skip', 'identical', 'force', 'conflict'][number]; export type ConflicterStatus = ConflicterLog | (typeof statusToSkipFile)[number]; export type ConflicterAction = 'write' | 'abort' | 'diff' | 'reload' | 'force' | 'edit' | 'ask' | 'skip' | 'ignore'; export declare function setConflicterStatus<F extends ConflicterFile = ConflicterFile>(file: F, status?: ConflicterStatus): F; type ConflicterData = { diskContents: Buffer; }; export type ConflicterFile = MemFsEditorFile & { conflicter?: ConflicterStatus; }; type ConflicterFileInternal = ConflicterFile & { relativePath: string; fileModeChanges?: [number, number]; changesDetected?: boolean; binary?: boolean; conflicterChanges?: Change[]; conflicterData?: ConflicterData; }; type ConflictedFile = ConflicterFileInternal & { conflicterChanges: Change[]; changesDetected: true; conflicterData: ConflicterData; }; type ActionCallbackOptions = { file: ConflicterFileInternal | ConflictedFile; relativeFilePath: string; adapter: InputOutputAdapter; }; type ValueActionCallback = (opt: ActionCallbackOptions) => ConflicterAction | Promise<ConflicterAction>; type ActionChoices = Parameters<typeof expand<ConflicterAction | ValueActionCallback>>[0]['choices']; type CustomizeActions = (actions: ActionChoices, options: { separator?: (separator?: string) => Separator; }) => ActionChoices; export type ConflicterOptions = { /** When set to true, we won't check for conflict. (the conflicter become a passthrough) */ force?: boolean; /** When set to true, we will abort on first conflict. (used for testing reproducibility) */ bail?: boolean; /** When set to true, whitespace changes should not generate a conflict. */ ignoreWhitespace?: boolean; /** When set to true, identical files should be written to disc. */ regenerate?: boolean; /** When set to true, no write operation will be executed. */ dryRun?: boolean; /** Path to be used as reference for relative path. */ cwd?: string; /** Custom diff options */ diffOptions?: DiffWordsOptionsNonabortable | DiffLinesOptionsNonabortable; /** Customize file actions */ customizeActions?: CustomizeActions; }; type ConflicterTransformOptions = { yoResolveFileName?: string; }; /** * The Conflicter is a module that can be used to detect conflict between files. Each * Generator file system helpers pass files through this module to make sure they don't * break a user file. * * When a potential conflict is detected, we prompt the user and ask them for * confirmation before proceeding with the actual write. */ export declare class Conflicter { private readonly adapter; force: boolean; bail: boolean; ignoreWhitespace: boolean; regenerate: boolean; dryRun: boolean; cwd: string; diffOptions?: DiffWordsOptionsNonabortable | DiffLinesOptionsNonabortable; customizeActions: CustomizeActions; constructor(adapter: InputOutputAdapter, options?: ConflicterOptions); private log; /** * Print the file differences to console * * @param {Object} file File object respecting this interface: { path, contents } */ private _printDiff; /** * Detect conflicts between file contents at `filepath` with the `contents` passed to the * function * * If `filepath` points to a folder, we'll always return true. * * Based on detect-conflict module * * @param {import('vinyl')} file File object respecting this interface: { path, contents } * @return {Boolean} `true` if there's a conflict, `false` otherwise. */ private _detectConflict; /** * Check if a file conflict with the current version on the user disk * * A basic check is done to see if the file exists, if it does: * * 1. Read its content from `fs` * 2. Compare it with the provided content * 3. If identical, mark it as is and skip the check * 4. If diverged, prepare and show up the file collision menu * * @param file - Vinyl file * @return Promise the Vinyl file */ private checkForCollision; private _checkForCollision; private ask; /** * Actual prompting logic * @private * @param {import('vinyl')} file vinyl file object * @param {Number} counter prompts */ private _ask; createTransform({ yoResolveFileName }?: ConflicterTransformOptions): FileTransform<MemFsEditorFile>; } export declare const createConflicterTransform: (adapter: InputOutputAdapter, { yoResolveFileName, ...conflicterOptions }?: ConflicterOptions & ConflicterTransformOptions) => FileTransform<MemFsEditorFile>; export {};