pretty-quick
Version:
Get Pretty Quick
284 lines (272 loc) • 9.97 kB
JavaScript
import fs from "fs";
import path from "path";
import ignore from "ignore";
import picomatch from "picomatch";
import fs$1 from "fs/promises";
import { check, format, getFileInfo, resolveConfig } from "prettier";
import { findUp } from "@pkgr/core";
import { exec } from "tinyexec";
//#region rolldown:runtime
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name$2 in all) __defProp(target, name$2, {
get: all[name$2],
enumerable: true
});
};
//#endregion
//#region src/createIgnorer.ts
function createIgnorer(directory, filename = ".prettierignore") {
const file = path.join(directory, filename);
if (fs.existsSync(file)) {
const text = fs.readFileSync(file, "utf8");
const filter = ignore().add(text).createFilter();
return (filepath) => filter(path.join(filepath));
}
return () => true;
}
var init_createIgnorer = __esm({ "src/createIgnorer.ts"() {} });
//#endregion
//#region src/createMatcher.ts
function createMatcher(pattern) {
if (typeof pattern !== "string" && !Array.isArray(pattern)) return () => true;
const patterns = Array.isArray(pattern) ? pattern : [pattern];
const isMatch = picomatch(patterns, { dot: true });
return (file) => isMatch(path.normalize(file));
}
var init_createMatcher = __esm({ "src/createMatcher.ts"() {} });
//#endregion
//#region src/isSupportedExtension.ts
var isSupportedExtension, isSupportedExtension_default;
var init_isSupportedExtension = __esm({ "src/isSupportedExtension.ts"() {
isSupportedExtension = (resolveConfig$1) => async (file) => {
const stat = await fs$1.stat(file).catch((_error) => null);
if (stat === null || stat === void 0 ? void 0 : stat.isDirectory()) return false;
const config = await resolveConfig(file, { editorconfig: true });
const fileInfo = await getFileInfo(file, {
resolveConfig: resolveConfig$1,
...config
});
return !!fileInfo.inferredParser;
};
isSupportedExtension_default = isSupportedExtension;
} });
//#endregion
//#region src/processFiles.ts
async function processFiles(directory, files, { check: check$1, config, onExamineFile, onCheckFile, onWriteFile } = {}) {
for (const relative of files) {
onExamineFile === null || onExamineFile === void 0 || onExamineFile(relative);
const file = path.join(directory, relative);
const options = {
...await resolveConfig(file, {
config,
editorconfig: true
}),
filepath: file
};
const input = fs.readFileSync(file, "utf8");
if (check$1) {
const isFormatted = await check(input, options);
onCheckFile === null || onCheckFile === void 0 || onCheckFile(relative, isFormatted);
continue;
}
const output = await format(input, options);
if (output !== input) {
fs.writeFileSync(file, output);
await (onWriteFile === null || onWriteFile === void 0 ? void 0 : onWriteFile(relative));
}
}
}
var init_processFiles = __esm({ "src/processFiles.ts"() {} });
//#endregion
//#region src/scms/git.ts
var git_exports = {};
__export(git_exports, {
detect: () => detect$1,
getChangedFiles: () => getChangedFiles$1,
getSinceRevision: () => getSinceRevision$1,
getUnstagedChangedFiles: () => getUnstagedChangedFiles$1,
name: () => name$1,
stageFile: () => stageFile$1
});
var name$1, detect$1, runGit, getLines$1, getSinceRevision$1, getChangedFiles$1, getUnstagedChangedFiles$1, stageFile$1;
var init_git = __esm({ "src/scms/git.ts"() {
name$1 = "git";
detect$1 = (directory) => {
const found = findUp(path.resolve(directory), ".git", true);
return found ? path.dirname(found) : null;
};
runGit = (directory, args) => exec("git", args, { nodeOptions: { cwd: directory } });
getLines$1 = (tinyexecOutput) => tinyexecOutput.stdout.split("\n");
getSinceRevision$1 = async (directory, { staged, branch }) => {
try {
let revision = "HEAD";
if (!staged) {
const revisionOutput = await runGit(directory, [
"merge-base",
"HEAD",
branch || "master"
]);
revision = revisionOutput.stdout.trim();
}
const revParseOutput = await runGit(directory, [
"rev-parse",
"--short",
revision
]);
return revParseOutput.stdout.trim();
} catch (err) {
const error = err;
if (/HEAD/.test(error.message) || staged && /Needed a single revision/.test(error.message)) return null;
throw error;
}
};
getChangedFiles$1 = async (directory, revision, staged) => [...getLines$1(await runGit(directory, [
"diff",
"--name-only",
staged ? "--cached" : null,
"--diff-filter=ACMRTUB",
revision
].filter(Boolean))), ...staged ? [] : getLines$1(await runGit(directory, [
"ls-files",
"--others",
"--exclude-standard"
]))].filter(Boolean);
getUnstagedChangedFiles$1 = (directory) => {
return getChangedFiles$1(directory, null, false);
};
stageFile$1 = (directory, file) => runGit(directory, ["add", file]);
} });
//#endregion
//#region src/scms/hg.ts
var hg_exports = {};
__export(hg_exports, {
detect: () => detect,
getChangedFiles: () => getChangedFiles,
getSinceRevision: () => getSinceRevision,
getUnstagedChangedFiles: () => getUnstagedChangedFiles,
name: () => name,
stageFile: () => stageFile
});
var name, detect, runHg, getLines, getSinceRevision, getChangedFiles, getUnstagedChangedFiles, stageFile;
var init_hg = __esm({ "src/scms/hg.ts"() {
name = "hg";
detect = (directory) => {
const found = findUp(path.resolve(directory), ".hg", true);
if (found && fs.statSync(found).isDirectory()) return path.dirname(found);
};
runHg = (directory, args) => exec("hg", args, { nodeOptions: { cwd: directory } });
getLines = (tinyexecOutput) => tinyexecOutput.stdout.split("\n");
getSinceRevision = async (directory, { branch }) => {
const revisionOutput = await runHg(directory, [
"debugancestor",
"tip",
branch || "default"
]);
const revision = revisionOutput.stdout.trim();
const hgOutput = await runHg(directory, [
"id",
"-i",
"-r",
revision
]);
return hgOutput.stdout.trim();
};
getChangedFiles = async (directory, revision, _staged) => [...getLines(await runHg(directory, [
"status",
"-n",
"-a",
"-m",
...revision ? ["--rev", revision] : []
]))].filter(Boolean);
getUnstagedChangedFiles = () => [];
stageFile = (directory, file) => runHg(directory, ["add", file]);
} });
//#endregion
//#region src/scms/index.ts
function detectScm(directory) {
for (const scm of scms) {
const rootDirectory = scm.detect(directory);
if (rootDirectory) return {
rootDirectory,
...scm
};
}
}
var scms;
var init_scms = __esm({ "src/scms/index.ts"() {
init_git();
init_hg();
scms = [git_exports, hg_exports];
} });
//#endregion
//#region src/utils.ts
var filterAsync;
var init_utils = __esm({ "src/utils.ts"() {
filterAsync = async (items, predicate) => {
const boolItems = await Promise.all(items.map(predicate));
return items.filter((_, i) => boolItems[i]);
};
} });
//#endregion
//#region src/index.ts
var require_src = __commonJS({ "src/index.ts"(exports, module) {
init_createIgnorer();
init_createMatcher();
init_isSupportedExtension();
init_processFiles();
init_scms();
init_utils();
module.exports = async function prettyQuick(currentDirectory, { config, since, staged, pattern, restage = true, branch, bail, check: check$1, ignorePath, verbose, onFoundSinceRevision, onFoundChangedFiles, onPartiallyStagedFile, onExamineFile, onCheckFile, onWriteFile, resolveConfig: resolveConfig$1 = true } = {}) {
const scm = detectScm(currentDirectory);
if (!scm) throw new Error("Unable to detect a source control manager.");
const directory = scm.rootDirectory;
const revision = since || await scm.getSinceRevision(directory, {
staged,
branch
});
onFoundSinceRevision === null || onFoundSinceRevision === void 0 || onFoundSinceRevision(scm.name, revision);
const rootIgnorer = createIgnorer(directory, ignorePath);
const cwdIgnorer = currentDirectory === directory ? () => true : createIgnorer(currentDirectory, ignorePath);
const patternMatcher = createMatcher(pattern);
const isFileSupportedExtension = isSupportedExtension_default(resolveConfig$1);
const unfilteredChangedFiles = await scm.getChangedFiles(directory, revision, staged);
const changedFiles = await filterAsync(unfilteredChangedFiles.filter(patternMatcher).filter(rootIgnorer).filter(cwdIgnorer), isFileSupportedExtension);
const unfilteredStagedFiles = await scm.getUnstagedChangedFiles(directory);
const unstagedFiles = staged ? await filterAsync(unfilteredStagedFiles.filter(patternMatcher).filter(rootIgnorer).filter(cwdIgnorer), isFileSupportedExtension) : [];
const wasFullyStaged = (file) => !unstagedFiles.includes(file);
onFoundChangedFiles === null || onFoundChangedFiles === void 0 || onFoundChangedFiles(changedFiles);
const failReasons = /* @__PURE__ */ new Set();
await processFiles(directory, changedFiles, {
check: check$1,
config,
onWriteFile: async (file) => {
await (onWriteFile === null || onWriteFile === void 0 ? void 0 : onWriteFile(file));
if (bail) failReasons.add("BAIL_ON_WRITE");
if (staged && restage) if (wasFullyStaged(file)) await scm.stageFile(directory, file);
else {
onPartiallyStagedFile === null || onPartiallyStagedFile === void 0 || onPartiallyStagedFile(file);
failReasons.add("PARTIALLY_STAGED_FILE");
}
},
onCheckFile: (file, isFormatted) => {
onCheckFile === null || onCheckFile === void 0 || onCheckFile(file, isFormatted);
if (!isFormatted) failReasons.add("CHECK_FAILED");
},
onExamineFile: verbose ? onExamineFile : void 0
});
return {
success: failReasons.size === 0,
errors: [...failReasons]
};
};
} });
//#endregion
export default require_src();