Refactor mockable functionalities (#17392)
This commit is contained in:
1 parent
369817b61f
commit
e7202d63e7
18 files changed
+280
-162
No files matched your search
+13
-11
@@ -5,19 +5,16 @@ import * as prettier from "../index.js";
|
||||
import { expandPatterns } from "./expand-patterns.js";
|
||||
import findCacheFile from "./find-cache-file.js";
|
||||
import FormatResultsCache from "./format-results-cache.js";
|
||||
import isTTY from "./is-tty.js";
|
||||
import mockable from "./mockable.js";
|
||||
import getOptionsForFile from "./options/get-options-for-file.js";
|
||||
import {
|
||||
createIsIgnoredFunction,
|
||||
createTwoFilesPatch,
|
||||
errors,
|
||||
mockable,
|
||||
picocolors,
|
||||
} from "./prettier-internal.js";
|
||||
import { normalizeToPosix, statSafe } from "./utils.js";
|
||||
|
||||
const { writeFormattedFile, getTimestamp } = mockable;
|
||||
|
||||
function diff(a, b) {
|
||||
return createTwoFilesPatch("", "", a, b, "", "", { context: 2 });
|
||||
}
|
||||
@@ -33,7 +30,7 @@ function handleError(context, filename, error, printedFilename, ignoreUnknown) {
|
||||
error instanceof errors.UndefinedParserError;
|
||||
|
||||
if (printedFilename) {
|
||||
// Can't test on CI, `isTTY()` is always false, see ./is-tty.js
|
||||
// Can't test on CI, `isTTY` is always false, see comments in `formatFiles`
|
||||
/* c8 ignore next 3 */
|
||||
if ((context.argv.write || ignoreUnknown) && errorIsUndefinedParseError) {
|
||||
printedFilename.clear();
|
||||
@@ -193,11 +190,11 @@ async function format(context, input, opt) {
|
||||
context.logger.debug(
|
||||
`'${performanceTestFlag.name}' found, running formatWithCursor ${repeat} times.`,
|
||||
);
|
||||
const start = getTimestamp();
|
||||
const start = mockable.getTimestamp();
|
||||
for (let i = 0; i < repeat; ++i) {
|
||||
await prettier.formatWithCursor(input, opt);
|
||||
}
|
||||
const averageMs = (getTimestamp() - start) / repeat;
|
||||
const averageMs = (mockable.getTimestamp() - start) / repeat;
|
||||
const results = {
|
||||
repeat,
|
||||
hz: 1000 / averageMs,
|
||||
@@ -303,6 +300,11 @@ async function formatFiles(context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Some CI pipelines incorrectly report process.stdout.isTTY status,
|
||||
// which causes unwanted lines in the output. An additional check for isCI() helps.
|
||||
// See https://github.com/prettier/prettier/issues/5801
|
||||
const isTTY = mockable.isStreamTTY(process.stdout) && !mockable.isCI();
|
||||
|
||||
for await (const { error, filename, ignoreUnknown } of expandPatterns(
|
||||
context,
|
||||
)) {
|
||||
@@ -331,7 +333,7 @@ async function formatFiles(context) {
|
||||
|
||||
const fileNameToDisplay = normalizeToPosix(path.relative(cwd, filename));
|
||||
let printedFilename;
|
||||
if (isTTY()) {
|
||||
if (isTTY) {
|
||||
printedFilename = context.logger.log(fileNameToDisplay, {
|
||||
newline: false,
|
||||
clearable: true,
|
||||
@@ -363,7 +365,7 @@ async function formatFiles(context) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const start = getTimestamp();
|
||||
const start = mockable.getTimestamp();
|
||||
|
||||
const isCacheExists = formatResultsCache?.existsAvailableFormatResultsCache(
|
||||
filename,
|
||||
@@ -405,7 +407,7 @@ async function formatFiles(context) {
|
||||
}
|
||||
|
||||
if (context.argv.write) {
|
||||
const timeToDisplay = `${Math.round(getTimestamp() - start)}ms`;
|
||||
const timeToDisplay = `${Math.round(mockable.getTimestamp() - start)}ms`;
|
||||
// Don't write the file if it won't change in order not to invalidate
|
||||
// mtime based caches.
|
||||
if (isDifferent) {
|
||||
@@ -414,7 +416,7 @@ async function formatFiles(context) {
|
||||
}
|
||||
|
||||
try {
|
||||
await writeFormattedFile(filename, output);
|
||||
await mockable.writeFormattedFile(filename, output);
|
||||
|
||||
// Set cache if format succeeds
|
||||
shouldSetCache = true;
|
||||
|
||||
+5
-1
@@ -4,6 +4,7 @@ import logFileInfoOrDie from "./file-info.js";
|
||||
import logResolvedConfigPathOrDie from "./find-config-path.js";
|
||||
import { formatFiles, formatStdin } from "./format.js";
|
||||
import createLogger from "./logger.js";
|
||||
import mockable from "./mockable.js";
|
||||
import { parseArgvWithoutPlugins } from "./options/parse-cli-arguments.js";
|
||||
import printSupportInfo from "./print-support-info.js";
|
||||
import { createDetailedUsage, createUsage } from "./usage.js";
|
||||
@@ -88,7 +89,8 @@ async function main(context) {
|
||||
|
||||
const hasFilePatterns = context.filePatterns.length > 0;
|
||||
const useStdin =
|
||||
!hasFilePatterns && (!process.stdin.isTTY || context.argv.filepath);
|
||||
!hasFilePatterns &&
|
||||
(!mockable.isStreamTTY(process.stdin) || context.argv.filepath);
|
||||
|
||||
if (useStdin) {
|
||||
if (context.argv.cache) {
|
||||
@@ -109,3 +111,5 @@ async function main(context) {
|
||||
}
|
||||
|
||||
export { run };
|
||||
// Exposed for tests
|
||||
export { mockable } from "./mockable.js";
|
||||
@@ -1,8 +0,0 @@
|
||||
import { mockable } from "./prettier-internal.js";
|
||||
|
||||
// Some CI pipelines incorrectly report process.stdout.isTTY status,
|
||||
// which causes unwanted lines in the output. An additional check for isCI() helps.
|
||||
// See https://github.com/prettier/prettier/issues/5801
|
||||
export default function isTTY() {
|
||||
return process.stdout.isTTY && !mockable.isCI();
|
||||
}
|
||||
+2
-27
@@ -1,31 +1,6 @@
|
||||
import readline from "node:readline";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import wcwidth from "wcwidth.js";
|
||||
import mockable from "./mockable.js";
|
||||
import { picocolors } from "./prettier-internal.js";
|
||||
|
||||
const countLines = (stream, text) => {
|
||||
const columns = stream.columns || 80;
|
||||
let lineCount = 0;
|
||||
for (const line of stripAnsi(text).split("\n")) {
|
||||
lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns));
|
||||
}
|
||||
return lineCount;
|
||||
};
|
||||
|
||||
const clear = (stream, text) => () => {
|
||||
const lineCount = countLines(stream, text);
|
||||
|
||||
for (let line = 0; line < lineCount; line++) {
|
||||
/* c8 ignore next 3 */
|
||||
if (line > 0) {
|
||||
readline.moveCursor(stream, 0, -1);
|
||||
}
|
||||
|
||||
readline.clearLine(stream, 0);
|
||||
readline.cursorTo(stream, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const emptyLogResult = { clear() {} };
|
||||
function createLogger(logLevel = "log") {
|
||||
return {
|
||||
@@ -56,7 +31,7 @@ function createLogger(logLevel = "log") {
|
||||
|
||||
if (options.clearable) {
|
||||
return {
|
||||
clear: clear(stream, message),
|
||||
clear: () => mockable.clearStreamText(stream, message),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { isCI } from "ci-info";
|
||||
// @ts-expect-error
|
||||
import { __internal as sharedWithCli } from "../index.js";
|
||||
import clearStreamText from "./utilities/clear-stream-text.js";
|
||||
|
||||
const mockable = sharedWithCli.utils.createMockable({
|
||||
clearStreamText,
|
||||
getTimestamp: performance.now.bind(performance),
|
||||
isCI: () => isCI,
|
||||
isStreamTTY: (stream) => stream.isTTY,
|
||||
writeFormattedFile: (file, data) => fs.writeFile(file, data),
|
||||
});
|
||||
|
||||
export default mockable.mocked;
|
||||
export { mockable };
|
||||
@@ -12,7 +12,6 @@ export const {
|
||||
vnopts,
|
||||
fastGlob,
|
||||
createTwoFilesPatch,
|
||||
mockable,
|
||||
picocolors,
|
||||
leven,
|
||||
} = sharedWithCli;
|
||||
@@ -0,0 +1,28 @@
|
||||
import readline from "node:readline";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import wcwidth from "wcwidth.js";
|
||||
|
||||
const countLines = (stream, text) => {
|
||||
const columns = stream.columns || 80;
|
||||
let lineCount = 0;
|
||||
for (const line of stripAnsi(text).split("\n")) {
|
||||
lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns));
|
||||
}
|
||||
return lineCount;
|
||||
};
|
||||
|
||||
function clearStreamText(stream, text) {
|
||||
const lineCount = countLines(stream, text);
|
||||
|
||||
for (let line = 0; line < lineCount; line++) {
|
||||
/* c8 ignore next 3 */
|
||||
if (line > 0) {
|
||||
readline.moveCursor(stream, 0, -1);
|
||||
}
|
||||
|
||||
readline.clearLine(stream, 0);
|
||||
readline.cursorTo(stream, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export default clearStreamText;
|
||||
+5
-13
@@ -1,16 +1,8 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { isCI } from "ci-info";
|
||||
import createMockable from "../utils/create-mockable.js";
|
||||
|
||||
function writeFormattedFile(file, data) {
|
||||
return fs.writeFile(file, data);
|
||||
}
|
||||
|
||||
const mockable = {
|
||||
const mockable = createMockable({
|
||||
getPrettierConfigSearchStopDirectory: () => undefined,
|
||||
isCI: () => isCI,
|
||||
writeFormattedFile,
|
||||
getTimestamp: performance.now.bind(performance),
|
||||
};
|
||||
});
|
||||
|
||||
export default mockable;
|
||||
export default mockable.mocked;
|
||||
export { mockable };
|
||||
+4
-2
@@ -12,7 +12,7 @@ import picocolors from "picocolors";
|
||||
import * as vnopts from "vnopts";
|
||||
import * as errors from "./common/errors.js";
|
||||
import getFileInfoWithoutPlugins from "./common/get-file-info.js";
|
||||
import mockable from "./common/mockable.js";
|
||||
import { mockable } from "./common/mockable.js";
|
||||
import {
|
||||
clearCache as clearConfigCache,
|
||||
resolveConfig,
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
getSupportInfo as getSupportInfoWithoutPlugins,
|
||||
normalizeOptionSettings,
|
||||
} from "./main/support.js";
|
||||
import createMockable from "./utils/create-mockable.js";
|
||||
import { createIsIgnoredFunction } from "./utils/ignore.js";
|
||||
import inferParserWithoutPlugins from "./utils/infer-parser.js";
|
||||
import omit from "./utils/object-omit.js";
|
||||
@@ -114,8 +115,8 @@ const sharedWithCli = {
|
||||
leven,
|
||||
utils: {
|
||||
omit,
|
||||
createMockable,
|
||||
},
|
||||
mockable,
|
||||
};
|
||||
|
||||
const debugApis = {
|
||||
@@ -124,6 +125,7 @@ const debugApis = {
|
||||
formatDoc: withPlugins(core.formatDoc),
|
||||
printToDoc: withPlugins(core.printToDoc),
|
||||
printDocToString: withPlugins(core.printDocToString),
|
||||
// Exposed for tests
|
||||
mockable,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
@template {{ [key: string]: any }} T
|
||||
@template {keyof T} K
|
||||
@param {T} implementations
|
||||
@returns {{
|
||||
mocked: T,
|
||||
implementations: T,
|
||||
mockImplementation(functionality: K, implementation: T[K]): void,
|
||||
mockImplementations(overrideImplementations: T): void,
|
||||
mockRestore(): void,
|
||||
}}
|
||||
*/
|
||||
function createMockable(implementations) {
|
||||
const mocked = { ...implementations };
|
||||
const mockImplementation = (functionality, implementation) => {
|
||||
if (!Object.hasOwn(implementations, functionality)) {
|
||||
throw new Error(`Unexpected mock '${functionality}'.`);
|
||||
}
|
||||
// @ts-expect-error -- ??
|
||||
mocked[functionality] = implementation;
|
||||
};
|
||||
const mockImplementations = (overrideImplementations) => {
|
||||
for (const [functionality, implementation] of Object.entries(
|
||||
overrideImplementations,
|
||||
)) {
|
||||
mockImplementation(functionality, implementation);
|
||||
}
|
||||
};
|
||||
const mockRestore = () => {
|
||||
Object.assign(mocked, implementations);
|
||||
};
|
||||
|
||||
return {
|
||||
mocked,
|
||||
implementations,
|
||||
mockImplementation,
|
||||
mockImplementations,
|
||||
mockRestore,
|
||||
};
|
||||
}
|
||||
|
||||
export default createMockable;
|
||||
@@ -15,7 +15,7 @@ exports[`Line breaking after filepath with errors 1`] = `
|
||||
invalid-2.js
|
||||
invalid-2.unknown
|
||||
valid-1.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): valid-1.js]]
|
||||
function foo() {}",
|
||||
"write": [],
|
||||
}
|
||||
@@ -36,8 +36,7 @@ exports[`Line breaking after filepath with errors 2`] = `
|
||||
invalid-2.js
|
||||
invalid-2.unknown
|
||||
valid-1.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
",
|
||||
[[Clear text(process.stdout): valid-1.js]]",
|
||||
"write": [],
|
||||
}
|
||||
`;
|
||||
@@ -58,7 +57,7 @@ invalid-1.js
|
||||
invalid-2.js
|
||||
invalid-2.unknown
|
||||
valid-1.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): valid-1.js]]
|
||||
All matched files use Prettier code style!",
|
||||
"write": [],
|
||||
}
|
||||
@@ -78,9 +77,9 @@ exports[`Line breaking after filepath with errors 4`] = `
|
||||
"stdout": "invalid-1.js
|
||||
invalid-2.js
|
||||
invalid-2.unknown
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): invalid-2.unknown]]
|
||||
valid-1.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): valid-1.js]]
|
||||
valid-1.js 0ms (unchanged)",
|
||||
"write": [],
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ exports[`no file diffs with --check + formatted file 1`] = `
|
||||
"stderr": "",
|
||||
"stdout": "Checking formatting...
|
||||
formatted.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): formatted.js]]
|
||||
All matched files use Prettier code style!",
|
||||
"write": [],
|
||||
}
|
||||
@@ -24,8 +24,7 @@ exports[`no file diffs with --list-different + formatted file 1`] = `
|
||||
{
|
||||
"stderr": "",
|
||||
"stdout": "formatted.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
",
|
||||
[[Clear text(process.stdout): formatted.js]]",
|
||||
"write": [],
|
||||
}
|
||||
`;
|
||||
@@ -44,8 +43,7 @@ exports[`output with --check + unformatted differs when piped 1`] = `
|
||||
[warn] Code style issues fixed in the above file.",
|
||||
"stdout": "Checking formatting...
|
||||
unformatted.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
",
|
||||
[[Clear text(process.stdout): unformatted.js]]",
|
||||
"write": [
|
||||
{
|
||||
"content": "var x = 1;
|
||||
@@ -75,7 +73,7 @@ exports[`output with --list-different + unformatted differs when piped 1`] = `
|
||||
{
|
||||
"stderr": "",
|
||||
"stdout": "unformatted.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): unformatted.js]]
|
||||
unformatted.js",
|
||||
"write": [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`Line breaking after filepath with errors 1`] = `
|
||||
{
|
||||
"stdout": "ignored.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): ignored.js]]
|
||||
foo(
|
||||
)",
|
||||
}
|
||||
@@ -12,7 +12,7 @@ foo(
|
||||
exports[`Line breaking after filepath with errors 2`] = `
|
||||
{
|
||||
"stdout": "not-ignored.js
|
||||
[[called readline.clearLine(process.stdout)]]
|
||||
[[Clear text(process.stdout): not-ignored.js]]
|
||||
foo();",
|
||||
}
|
||||
`;
|
||||
@@ -1,8 +1,10 @@
|
||||
import fs from "node:fs";
|
||||
import { prettierCli } from "../env.js";
|
||||
import { prettierCliEntry } from "../env.js";
|
||||
|
||||
describe("CLI", () => {
|
||||
test("CLI should be executable.", () => {
|
||||
expect(() => fs.accessSync(prettierCli, fs.constants.X_OK)).not.toThrow();
|
||||
expect(() =>
|
||||
fs.accessSync(prettierCliEntry, fs.constants.X_OK),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,12 @@
|
||||
import prettier from "../../config/prettier-entry.js";
|
||||
import url from "node:url";
|
||||
import { prettierCliMockableEntry } from "../env.js";
|
||||
|
||||
const { mockable } = prettier.__debug;
|
||||
async function getCliMockable() {
|
||||
const cli = await import(url.pathToFileURL(prettierCliMockableEntry));
|
||||
return cli.mockable;
|
||||
}
|
||||
|
||||
test("isCI", () => {
|
||||
expect(typeof mockable.isCI()).toBe("boolean");
|
||||
test("isCI", async () => {
|
||||
const mockable = await getCliMockable();
|
||||
expect(typeof mockable.implementations.isCI()).toBe("boolean");
|
||||
});
|
||||
@@ -1,7 +1,10 @@
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import url from "node:url";
|
||||
import { prettierCli, prettierMainEntry } from "./env.js";
|
||||
import {
|
||||
prettierCliEntry,
|
||||
prettierCliMockableEntry,
|
||||
prettierMainEntry,
|
||||
} from "./env.js";
|
||||
|
||||
const normalizeToPosix =
|
||||
path.sep === "\\"
|
||||
@@ -17,55 +20,83 @@ const replaceAll = (text, find, replacement) =>
|
||||
? text.replaceAll(find, replacement)
|
||||
: text.split(find).join(replacement);
|
||||
|
||||
async function run(options) {
|
||||
readline.clearLine = (stream) => {
|
||||
stream.write(
|
||||
`\n[[called readline.clearLine(${
|
||||
async function getApiMockable() {
|
||||
const prettier = await import(url.pathToFileURL(prettierMainEntry));
|
||||
return prettier.__debug.mockable;
|
||||
}
|
||||
|
||||
async function getCliMockable() {
|
||||
const cli = await import(url.pathToFileURL(prettierCliMockableEntry));
|
||||
return cli.mockable;
|
||||
}
|
||||
|
||||
async function mockImplementations(options) {
|
||||
const [apiMockable, cliMockable] = await Promise.all([
|
||||
getApiMockable(),
|
||||
getCliMockable(),
|
||||
]);
|
||||
|
||||
cliMockable.mockImplementations({
|
||||
clearStreamText(stream, text) {
|
||||
const streamName =
|
||||
stream === process.stdout
|
||||
? "process.stdout"
|
||||
: stream === process.stderr
|
||||
? "process.stderr"
|
||||
: "unknown stream"
|
||||
})]]\n`,
|
||||
);
|
||||
};
|
||||
: "unknown stream";
|
||||
stream.write(`\n[[Clear text(${streamName}): ${text}]]\n`);
|
||||
},
|
||||
// Time measure in format test
|
||||
getTimestamp: () => 0,
|
||||
isCI: () => Boolean(options.ci),
|
||||
isStreamTTY: (stream) =>
|
||||
stream === process.stdin
|
||||
? Boolean(options.isTTY)
|
||||
: stream === process.stdout
|
||||
? Boolean(options.stdoutIsTTY)
|
||||
: stream.isTTY,
|
||||
// eslint-disable-next-line require-await
|
||||
async writeFormattedFile(filename, content) {
|
||||
filename = normalizeToPosix(path.relative(process.cwd(), filename));
|
||||
if (
|
||||
options.mockWriteFileErrors &&
|
||||
hasOwn(options.mockWriteFileErrors, filename)
|
||||
) {
|
||||
throw new Error(
|
||||
options.mockWriteFileErrors[filename] + " (mocked error)",
|
||||
);
|
||||
}
|
||||
process.send({
|
||||
type: "cli:write-file",
|
||||
data: { filename, content },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
process.stdin.isTTY = Boolean(options.isTTY);
|
||||
process.stdout.isTTY = Boolean(options.stdoutIsTTY);
|
||||
apiMockable.mockImplementations({
|
||||
getPrettierConfigSearchStopDirectory: () =>
|
||||
url.fileURLToPath(new URL("./cli", import.meta.url)),
|
||||
});
|
||||
}
|
||||
|
||||
const prettier = await import(url.pathToFileURL(prettierMainEntry));
|
||||
const { mockable } = prettier.__debug;
|
||||
async function run(options) {
|
||||
await mockImplementations(options);
|
||||
|
||||
// Time measure in format test
|
||||
mockable.getTimestamp = () => 0;
|
||||
mockable.isCI = () => Boolean(options.ci);
|
||||
mockable.getPrettierConfigSearchStopDirectory = () =>
|
||||
url.fileURLToPath(new URL("./cli", import.meta.url));
|
||||
// eslint-disable-next-line require-await
|
||||
mockable.writeFormattedFile = async (filename, content) => {
|
||||
filename = normalizeToPosix(path.relative(process.cwd(), filename));
|
||||
if (
|
||||
options.mockWriteFileErrors &&
|
||||
hasOwn(options.mockWriteFileErrors, filename)
|
||||
) {
|
||||
throw new Error(
|
||||
options.mockWriteFileErrors[filename] + " (mocked error)",
|
||||
);
|
||||
}
|
||||
|
||||
process.send({
|
||||
action: "write-file",
|
||||
data: { filename, content },
|
||||
});
|
||||
};
|
||||
|
||||
const { __promise: promise } = await import(url.pathToFileURL(prettierCli));
|
||||
const { __promise: promise } = await import(
|
||||
url.pathToFileURL(prettierCliEntry)
|
||||
);
|
||||
await promise;
|
||||
}
|
||||
|
||||
process.once("message", async (data) => {
|
||||
try {
|
||||
await run(data);
|
||||
} catch (error) {
|
||||
// For easier debugging
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
process.send({ type: "worker:fault", error });
|
||||
throw error;
|
||||
} finally {
|
||||
// On MacOS, if we exit too quick the stdio won't received on main thread
|
||||
if (process.platform === "darwin") {
|
||||
|
||||
@@ -6,7 +6,7 @@ const { __dirname, require } = createEsmUtils(import.meta);
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const { PRETTIER_DIR } = process.env;
|
||||
const { bin } = require(path.join(PRETTIER_DIR, "package.json"));
|
||||
const prettierCli = path.join(
|
||||
const prettierCliEntry = path.join(
|
||||
PRETTIER_DIR,
|
||||
typeof bin === "object" ? bin.prettier : bin,
|
||||
);
|
||||
@@ -17,4 +17,14 @@ const prettierMainEntry = isProduction
|
||||
|
||||
const projectRoot = path.join(__dirname, "../..");
|
||||
|
||||
export { isProduction, prettierCli, prettierMainEntry, projectRoot };
|
||||
const prettierCliMockableEntry = isProduction
|
||||
? path.join(PRETTIER_DIR, "./internal/legacy-cli.mjs")
|
||||
: path.join(PRETTIER_DIR, "./src/cli/index.js");
|
||||
|
||||
export {
|
||||
isProduction,
|
||||
prettierCliEntry,
|
||||
prettierCliMockableEntry,
|
||||
prettierMainEntry,
|
||||
projectRoot,
|
||||
};
|
||||
@@ -26,6 +26,16 @@ const removeFinalNewLine = (string) =>
|
||||
string.endsWith("\n") ? string.slice(0, -1) : string;
|
||||
const SUPPORTS_DISABLE_WARNING_FLAG =
|
||||
Number(process.versions.node.split(".")[0]) >= 20;
|
||||
const promiseWithResolvers = Promise.withResolvers
|
||||
? Promise.withResolvers.bind(Promise)
|
||||
: () => {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise(
|
||||
(_resolve, _reject) => ([resolve, reject] = [_resolve, _reject]),
|
||||
);
|
||||
return { promise, resolve, reject };
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
@@ -39,6 +49,30 @@ function runCliWorker(dir, args, options) {
|
||||
stderr: "",
|
||||
write: [],
|
||||
};
|
||||
const { promise, resolve, reject } = promiseWithResolvers();
|
||||
const createEpipeErrorHandler = (event) => (error) => {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It can fail with `write EPIPE` error when running node with unsupported flags like `--experimental-strip-types`
|
||||
// Let's ignore and wait for the `close` event
|
||||
if (
|
||||
error.code === "EPIPE" &&
|
||||
error.syscall === "write" &&
|
||||
nodeOptions.length > 0
|
||||
) {
|
||||
if (IS_CI) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
Object.assign(error, { event, dir, args, options, worker }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const nodeOptions = options?.nodeOptions ?? [];
|
||||
const worker = childProcess.fork(CLI_WORKER_FILE, args, {
|
||||
@@ -55,11 +89,14 @@ function runCliWorker(dir, args, options) {
|
||||
...process.env,
|
||||
NO_COLOR: "1",
|
||||
},
|
||||
serialization: "advanced",
|
||||
});
|
||||
|
||||
worker.on("message", ({ action, data }) => {
|
||||
if (action === "write-file") {
|
||||
result.write.push(data);
|
||||
worker.on("message", (message) => {
|
||||
if (message.type === "cli:write-file") {
|
||||
result.write.push(message.data);
|
||||
} else if (message.type === "worker:fault") {
|
||||
reject(message.error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,49 +106,32 @@ function runCliWorker(dir, args, options) {
|
||||
});
|
||||
}
|
||||
|
||||
if (options.input) {
|
||||
worker.stdin.end(options.input);
|
||||
}
|
||||
|
||||
const removeStdioFinalNewLine = () => {
|
||||
for (const stream of ["stdout", "stderr"]) {
|
||||
result[stream] = removeFinalNewLine(result[stream]);
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.once("close", (code) => {
|
||||
result.status = code;
|
||||
removeStdioFinalNewLine();
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
worker.once("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
worker.send(options, (error) => {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It can fail with `write EPIPE` error when running node with unsupported flags like `--experimental-strip-types`
|
||||
// Let's ignore and wait for the `close` event
|
||||
if (
|
||||
error.code === "EPIPE" &&
|
||||
error.syscall === "write" &&
|
||||
nodeOptions.length > 0
|
||||
) {
|
||||
if (IS_CI) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(Object.assign(error, { dir, args, options, worker }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
worker.once("close", (code) => {
|
||||
result.status = code;
|
||||
removeStdioFinalNewLine();
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
worker.once("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
worker.once("spawn", () => {
|
||||
if (options.input) {
|
||||
worker.stdin.once("error", createEpipeErrorHandler("worker.stdin.end()"));
|
||||
worker.stdin.end(options.input);
|
||||
}
|
||||
|
||||
worker.send(options, createEpipeErrorHandler("worker.send()"));
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function runPrettierCli(dir, args, options) {
|
||||
|
||||
Reference in new issue
Block a user