@yeoman/conflicter
Version:
Conflict resolution for yeoman's generator/environment stack
130 lines (129 loc) • 5.34 kB
TypeScript
/// <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 {};