UNPKG

@yeoman/conflicter

Version:

Conflict resolution for yeoman's generator/environment stack

133 lines (132 loc) 5.55 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 } 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 & { relativePath: string; conflicter?: ConflicterStatus; fileModeChanges?: [number, number]; changesDetected?: boolean; binary?: boolean; conflicterChanges?: Change[]; conflicterData?: ConflicterData; }; export type ConflictedFile = ConflicterFile & { conflicterChanges: Change[]; changesDetected: true; conflicterData: ConflicterData; }; type ActionCallbackOptions = { file: ConflicterFile | 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 = { force?: boolean; bail?: boolean; ignoreWhitespace?: boolean; regenerate?: boolean; dryRun?: boolean; cwd?: string; diffOptions?: any; 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. * * @constructor * @property {Boolean} force - same as the constructor argument * * @param {TerminalAdapter} adapter - The generator adapter * @param {Object} options - Conflicter options * @param {Boolean} [options.force=false] - When set to true, we won't check for conflict. (the conflicter become a passthrough) * @param {Boolean} [options.bail=false] - When set to true, we will abort on first conflict. (used for testing reproducibility) * @param {Boolean} [options.ignoreWhitespace=false] - When set to true, whitespace changes should not generate a conflict. * @param {Boolean} [options.regenerate=false] - When set to true, identical files should be written to disc. * @param {Boolean} [options.dryRun=false] - When set to true, no write operation will be executed. * @param {Boolean} [options.cwd=process.cwd()] - Path to be used as reference for relative path. * @param {string} cwd - Set cwd for relative logs. */ export declare class Conflicter { private readonly adapter; force: boolean; bail: boolean; ignoreWhitespace: boolean; regenerate: boolean; dryRun: boolean; cwd: string; diffOptions?: any; 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 */ checkForCollision(file: ConflicterFile): Promise<ConflicterFile>; 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 {};