Skip to content

Commit 028e631

Browse files
Added support for reading from stdin, and for the "--stdin-filepath" option
1 parent 6cb91c5 commit 028e631

File tree

6 files changed

+63
-12
lines changed

6 files changed

+63
-12
lines changed

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"kasi": "^1.1.0",
4747
"lomemo": "^1.0.0",
4848
"pioppo": "^1.1.1",
49+
"promise-resolve-timeout": "^2.0.0",
4950
"specialist": "^1.4.0",
5051
"tiny-editorconfig": "^1.0.0",
5152
"tiny-jsonc": "^1.0.1",

src/bin.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,13 @@ const makeBin = (): Bin => {
133133
"--require-pragma",
134134
'Require either "@prettier" or "@format" to be present in the file\'s first docblock comment in order for it to be formatted\nDefaults to "false"',
135135
)
136-
// .option(
137-
// "--stdin-filepath <path>",
138-
// "Path to the file to pretend that stdin comes from",
139-
// )
136+
.option("--stdin-filepath <path>", "Path to the file to pretend that stdin comes from")
140137
// .option("--support-info", "Print support information as JSON")
141138
/* DEFAULT COMMAND */
142139
.argument("[file/dir/glob...]", "Files, directories or globs to format")
143140
.action(async (options, files) => {
144141
const { run } = await import("./index.js");
145-
const baseOptions = normalizeOptions(options, files);
142+
const baseOptions = await normalizeOptions(options, files);
146143
const pluginsOptions = {};
147144
return run(baseOptions, pluginsOptions);
148145
})
@@ -205,7 +202,7 @@ const makePluggableBin = async (): Promise<Bin> => {
205202

206203
bin = bin.action(async (options, files) => {
207204
const { run } = await import("./index.js");
208-
const baseOptions = normalizeOptions(options, files);
205+
const baseOptions = await normalizeOptions(options, files);
209206
const pluginsDynamicOptions = normalizePluginOptions(options, optionsNames);
210207
const pluginsOptions = { ...pluginsStaticOptions, ...pluginsDynamicOptions };
211208
return run(baseOptions, pluginsOptions);

src/index.ts

+29-3
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,37 @@ import { PRETTIER_VERSION, CLI_VERSION } from "./constants.js";
1111
import Known from "./known.js";
1212
import Logger from "./logger.js";
1313
import { makePrettier } from "./prettier.js";
14-
import { castArray, getExpandedFoldersPaths, getFoldersChildrenPaths, getPluginsVersions, getProjectPath, getTargetsPaths } from "./utils.js";
14+
import { castArray, getExpandedFoldersPaths, getFoldersChildrenPaths, getPluginsVersions, getProjectPath, getStdin, getTargetsPaths } from "./utils.js";
1515
import { fastRelativePath, isString, isUndefined, negate, pluralize, uniq } from "./utils.js";
1616
import type { FormatOptions, Options, PluginsOptions } from "./types.js";
1717

18-
async function run(options: Options, pluginsOptions: PluginsOptions): Promise<void> {
18+
async function run(options: Options, pluginsOptions): Promise<void> {
19+
const stdin = await getStdin();
20+
if (isString(stdin)) {
21+
return runStdin(options, pluginsOptions);
22+
} else {
23+
return runGlobs(options, pluginsOptions);
24+
}
25+
}
26+
27+
async function runStdin(options: Options, pluginsOptions: PluginsOptions): Promise<void> {
28+
const logger = new Logger(options.logLevel);
29+
const prettier = await import("./prettier_serial.js");
30+
31+
const fileName = options.stdinFilepath || "stdin";
32+
const fileContent = (await getStdin()) || "";
33+
34+
try {
35+
const formatted = await prettier.format(fileName, fileContent, options.formatOptions, options.contextOptions, pluginsOptions);
36+
logger.always(formatted);
37+
process.exitCode = options.check && formatted !== fileContent ? 1 : 0;
38+
} catch (error) {
39+
logger.prefixed.error(String(error));
40+
process.exitCode = 1;
41+
}
42+
}
43+
44+
async function runGlobs(options: Options, pluginsOptions: PluginsOptions): Promise<void> {
1945
const logger = new Logger(options.logLevel);
2046
const spinner = options.check ? logger.spinner.log() : undefined;
2147

@@ -193,4 +219,4 @@ async function run(options: Options, pluginsOptions: PluginsOptions): Promise<vo
193219
process.exitCode = (!totalMatched && options.errorOnUnmatchedPattern) || totalErrored || (totalUnformatted && !options.write) ? 1 : 0;
194220
}
195221

196-
export { run };
222+
export { run, runStdin, runGlobs };

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type Options = {
6363
logLevel: LogLevel;
6464
parallel: boolean;
6565
parallelWorkers: number;
66+
stdinFilepath: string | undefined;
6667
/* CONTEXT OPTIONS */
6768
contextOptions: ContextOptions;
6869
/* FORMAT OPTIONS */

src/utils.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import crypto from "node:crypto";
66
import fs from "node:fs";
77
import path from "node:path";
88
import process from "node:process";
9+
import { text as stream2text } from "node:stream/consumers";
910
import url from "node:url";
11+
import resolveTimeout from "promise-resolve-timeout";
1012
import { exit } from "specialist";
1113
import readdir from "tiny-readdir-glob";
1214
import zeptomatchEscape from "zeptomatch-escape";
@@ -166,6 +168,15 @@ function getProjectPath(rootPath: string): string {
166168
}
167169
}
168170

171+
const getStdin = once(async (): Promise<string | undefined> => {
172+
// Without a TTY, the process is likely, but not certainly, being piped
173+
if (!process.stdin.isTTY) {
174+
const stdin = stream2text(process.stdin);
175+
const fallback = resolveTimeout(1_000, undefined);
176+
return Promise.race([stdin, fallback]);
177+
}
178+
});
179+
169180
async function getTargetsPaths(
170181
rootPath: string,
171182
globs: string[],
@@ -262,15 +273,16 @@ function noop(): undefined {
262273
return;
263274
}
264275

265-
function normalizeOptions(options: unknown, targets: unknown[]): Options {
276+
async function normalizeOptions(options: unknown, targets: unknown[]): Promise<Options> {
266277
if (!isObject(options)) exit("Invalid options object");
267278

268279
const targetsGlobs = targets.filter(isString);
269-
270280
const targetsStatic = "--" in options && Array.isArray(options["--"]) ? options["--"].filter(isString).map(zeptomatchEscape) : [];
271281
const globs = [...targetsGlobs, ...targetsStatic];
272282

273-
if (!globs.length) exit("Expected at least one target file/dir/glob");
283+
const stdin = await getStdin();
284+
285+
if (!isString(stdin) && !globs.length) exit("Expected at least one target file/dir/glob");
274286

275287
const check = "check" in options && !!options.check;
276288
const list = "listDifferent" in options && !!options.listDifferent;
@@ -296,6 +308,7 @@ function normalizeOptions(options: unknown, targets: unknown[]): Options {
296308
const logLevel = "logLevel" in options ? ((options.logLevel || "log") as LogLevel) : "log";
297309
const parallel = "parallel" in options && !!options.parallel;
298310
const parallelWorkers = ("parallelWorkers" in options && Math.round(Number(options.parallelWorkers))) || 0;
311+
const stdinFilepath = "stdinFilepath" in options && isString(options.stdinFilepath) ? options.stdinFilepath : undefined;
299312

300313
const contextOptions = normalizeContextOptions(options);
301314
const formatOptions = normalizeFormatOptions(options);
@@ -317,6 +330,7 @@ function normalizeOptions(options: unknown, targets: unknown[]): Options {
317330
logLevel,
318331
parallel,
319332
parallelWorkers,
333+
stdinFilepath,
320334
contextOptions,
321335
formatOptions,
322336
};
@@ -631,6 +645,7 @@ export {
631645
getPluginsPaths,
632646
getPluginsVersions,
633647
getProjectPath,
648+
getStdin,
634649
getTargetsPaths,
635650
isArray,
636651
isBoolean,

0 commit comments

Comments
 (0)