punctilio 5.4.8 → 5.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/markdown.js CHANGED
@@ -3,10 +3,10 @@ import remarkParse from "remark-parse";
3
3
  import remarkGfm from "remark-gfm";
4
4
  import remarkStringify from "remark-stringify";
5
5
  import QuickLRU from "quick-lru";
6
+ import { MARKDOWN_OPTION_KEYS } from "./sink-option-keys.js";
6
7
  import { remarkPunctilio } from "./remark.js";
7
- import { MARKDOWN_ONLY_OPTION_KEYS, MARKDOWN_OPTION_KEYS } from "./sink-option-keys.js";
8
- import { assertKnownOptionKeys, stableStringify } from "./utils.js";
9
- export { MARKDOWN_ONLY_OPTION_KEYS, MARKDOWN_OPTION_KEYS };
8
+ import { assertKnownOptionKeys, stableStringify, transformPreservingBom } from "./utils.js";
9
+ export { MARKDOWN_ONLY_OPTION_KEYS, MARKDOWN_OPTION_KEYS } from "./sink-option-keys.js";
10
10
  function createProcessor(options) {
11
11
  const { emphasisMarker, strongMarker, bulletMarker, ruleMarker, ...punctilioOptions } = options;
12
12
  return unified()
@@ -35,8 +35,40 @@ const processorCache = new QuickLRU({
35
35
  export function clearProcessorCache() {
36
36
  processorCache.clear();
37
37
  }
38
- export async function transformMarkdown(input, options = {}) {
39
- assertKnownOptionKeys(options, MARKDOWN_OPTION_KEYS, "transformMarkdown");
38
+ // Opening fence → fences that may close the block (YAML accepts `...` too).
39
+ // A Map, so a first line like `constructor` cannot resolve to an inherited
40
+ // Object.prototype member.
41
+ const FRONTMATTER_CLOSING_FENCES = new Map([
42
+ ["---", ["---", "..."]],
43
+ ["+++", ["+++"]],
44
+ ]);
45
+ /**
46
+ * Length of the YAML (`---`) or TOML (`+++`) front matter block at the very
47
+ * start of `input`, through its closing fence line and newline. 0 when the
48
+ * input has no terminated front matter block.
49
+ */
50
+ function frontmatterLength(input) {
51
+ let lineStart = 0;
52
+ let closingFences;
53
+ while (lineStart < input.length) {
54
+ const newlineIndex = input.indexOf("\n", lineStart);
55
+ const lineEnd = newlineIndex === -1 ? input.length : newlineIndex + 1;
56
+ const fence = input.slice(lineStart, lineEnd).trimEnd();
57
+ if (closingFences === undefined) {
58
+ closingFences = FRONTMATTER_CLOSING_FENCES.get(fence);
59
+ if (closingFences === undefined)
60
+ return 0;
61
+ }
62
+ else if (closingFences.includes(fence)) {
63
+ return lineEnd;
64
+ }
65
+ lineStart = lineEnd;
66
+ }
67
+ return 0;
68
+ }
69
+ /** Matches blank lines that separate front matter from the document body. */
70
+ const LEADING_BLANK_LINES_RE = /^(?:[ \t]*\r?\n)*/;
71
+ async function processMarkdown(input, options) {
40
72
  const optionsKey = stableStringify(options);
41
73
  let processor = processorCache.get(optionsKey);
42
74
  if (!processor) {
@@ -46,4 +78,19 @@ export async function transformMarkdown(input, options = {}) {
46
78
  const result = await processor.process(input);
47
79
  return String(result);
48
80
  }
81
+ export async function transformMarkdown(input, options = {}) {
82
+ assertKnownOptionKeys(options, MARKDOWN_OPTION_KEYS, "transformMarkdown");
83
+ return transformPreservingBom(input, (text) => transformWithFrontmatter(text, options));
84
+ }
85
+ async function transformWithFrontmatter(input, options) {
86
+ const frontmatterEnd = frontmatterLength(input);
87
+ if (frontmatterEnd === 0)
88
+ return processMarkdown(input, options);
89
+ // Front matter is data (YAML/TOML), not prose; remark would parse it as a
90
+ // thematic break plus a heading and rewrite it.
91
+ const body = input.slice(frontmatterEnd);
92
+ const blankLines = LEADING_BLANK_LINES_RE.exec(body)[0];
93
+ const transformedBody = await processMarkdown(body.slice(blankLines.length), options);
94
+ return input.slice(0, frontmatterEnd) + blankLines + transformedBody;
95
+ }
49
96
  //# sourceMappingURL=markdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,WAAW,MAAM,cAAc,CAAA;AACtC,OAAO,SAAS,MAAM,YAAY,CAAA;AAClC,OAAO,eAAe,MAAM,kBAAkB,CAAA;AAC9C,OAAO,QAAQ,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACvF,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA2BnE,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,CAAA;AAE1D,SAAS,eAAe,CAAC,OAAwB;IAC/C,MAAM,EACJ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,GAAG,gBAAgB,EACpB,GAAG,OAAO,CAAA;IAEX,OAAO,OAAO,EAAE;SACb,GAAG,CAAC,WAAW,CAAC;SAChB,GAAG,CAAC,SAAS,CAAC;SACd,GAAG,CAAC,eAAe,EAAE,gBAAgB,CAAC;SACtC,GAAG,CAAC,eAAe,EAAE;QACpB,QAAQ,EAAE,cAAc,IAAI,GAAG;QAC/B,MAAM,EAAE,YAAY,IAAI,GAAG;QAC3B,MAAM,EAAE,YAAY,IAAI,GAAG;QAC3B,IAAI,EAAE,UAAU,IAAI,GAAG;QACvB,kEAAkE;QAClE,uDAAuD;QACvD,GAAG,CAAC,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChF,sEAAsE;QACtE,sEAAsE;QACtE,6CAA6C;QAC7C,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;KACtD,CAAC,CAAA;AACN,CAAC;AAED,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAElC,MAAM,cAAc,GAAG,IAAI,QAAQ,CAA6C;IAC9E,OAAO,EAAE,wBAAwB;CAClC,CAAC,CAAA;AAEF,gBAAgB;AAChB,MAAM,UAAU,mBAAmB;IACjC,cAAc,CAAC,KAAK,EAAE,CAAA;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAa,EACb,UAA2B,EAAE;IAE7B,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,CAAA;IAEzE,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;IAE3C,IAAI,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAC9C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;QACpC,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IAC3C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAA;AACvB,CAAC"}
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { HtmlOptions } from "./html.js";
2
+ import type { MarkdownOptions } from "./markdown.js";
3
+ export type SinkType = "md" | "html";
4
+ /** Imports a bare package specifier; injectable so tests can simulate an uninstalled peer. */
5
+ export type PeerImporter = (packageName: string) => Promise<unknown>;
6
+ export interface SinkTransformers {
7
+ md: (input: string, options: MarkdownOptions) => Promise<string>;
8
+ html: (input: string, options: HtmlOptions) => Promise<string>;
9
+ }
10
+ /** The optional peer dependencies each sink needs at runtime. */
11
+ export declare const SINK_PEER_DEPENDENCIES: Readonly<Record<SinkType, readonly string[]>>;
12
+ /** Thrown when an optional peer dependency needed for the requested file type is not installed. */
13
+ export declare class MissingPeerDependencyError extends Error {
14
+ }
15
+ /**
16
+ * Lazily loads the transform for `type`, so only the peers for the file type
17
+ * being processed need to be installed.
18
+ * @throws MissingPeerDependencyError naming every absent peer and the install command.
19
+ */
20
+ export declare function loadSinkTransform<T extends SinkType>(type: T, importPeer?: PeerImporter): Promise<SinkTransformers[T]>;
21
+ //# sourceMappingURL=peer-dependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-dependencies.d.ts","sourceRoot":"","sources":["../src/peer-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAA;AAEpC,8FAA8F;AAC9F,MAAM,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAChE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/D;AAED,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC,CAGhF,CAAA;AAED,mGAAmG;AACnG,qBAAa,0BAA2B,SAAQ,KAAK;CAAG;AAWxD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,QAAQ,EACxD,IAAI,EAAE,CAAC,EACP,UAAU,GAAE,YAA4B,GACvC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAkB9B"}
@@ -0,0 +1,40 @@
1
+ /** The optional peer dependencies each sink needs at runtime. */
2
+ export const SINK_PEER_DEPENDENCIES = {
3
+ md: ["unified", "remark-parse", "remark-gfm", "remark-stringify"],
4
+ html: ["unified", "rehype-parse", "rehype-stringify"],
5
+ };
6
+ /** Thrown when an optional peer dependency needed for the requested file type is not installed. */
7
+ export class MissingPeerDependencyError extends Error {
8
+ }
9
+ const MODULE_NOT_FOUND_CODES = new Set(["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND"]);
10
+ function isMissingPackage(err, packageName) {
11
+ const { code, message } = err;
12
+ return MODULE_NOT_FOUND_CODES.has(code ?? "") && message.includes(`'${packageName}'`);
13
+ }
14
+ const importPackage = (packageName) => import(packageName);
15
+ /**
16
+ * Lazily loads the transform for `type`, so only the peers for the file type
17
+ * being processed need to be installed.
18
+ * @throws MissingPeerDependencyError naming every absent peer and the install command.
19
+ */
20
+ export async function loadSinkTransform(type, importPeer = importPackage) {
21
+ const missing = [];
22
+ for (const packageName of SINK_PEER_DEPENDENCIES[type]) {
23
+ try {
24
+ await importPeer(packageName);
25
+ }
26
+ catch (err) {
27
+ if (!isMissingPackage(err, packageName))
28
+ throw err;
29
+ missing.push(packageName);
30
+ }
31
+ }
32
+ if (missing.length > 0) {
33
+ throw new MissingPeerDependencyError(`Processing ${type === "md" ? "Markdown" : "HTML"} files requires ${missing.join(", ")}, which ` +
34
+ `${missing.length === 1 ? "is" : "are"} not installed. Install with: npm install ${missing.join(" ")}`);
35
+ }
36
+ if (type === "md")
37
+ return (await import("./markdown.js")).transformMarkdown;
38
+ return (await import("./html.js")).transformHtml;
39
+ }
40
+ //# sourceMappingURL=peer-dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-dependencies.js","sourceRoot":"","sources":["../src/peer-dependencies.ts"],"names":[],"mappings":"AAaA,iEAAiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAkD;IACnF,EAAE,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,kBAAkB,CAAC;IACjE,IAAI,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,kBAAkB,CAAC;CACtD,CAAA;AAED,mGAAmG;AACnG,MAAM,OAAO,0BAA2B,SAAQ,KAAK;CAAG;AAExD,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,CAAC,CAAA;AAEpF,SAAS,gBAAgB,CAAC,GAAY,EAAE,WAAmB;IACzD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAA4B,CAAA;IACtD,OAAO,sBAAsB,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,WAAW,GAAG,CAAC,CAAA;AACvF,CAAC;AAED,MAAM,aAAa,GAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;AAExE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAO,EACP,aAA2B,aAAa;IAExC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,WAAW,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,WAAW,CAAC,CAAA;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,WAAW,CAAC;gBAAE,MAAM,GAAG,CAAA;YAClD,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,0BAA0B,CAClC,cAAc,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU;YAChG,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,6CAA6C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACvG,CAAA;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,CAAC,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAwC,CAAA;IAClG,OAAO,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,aAAoC,CAAA;AACzE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"prettier-plugin.d.ts","sourceRoot":"","sources":["../src/prettier-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,UAAU,CAAA;AAkC9C,QAAA,MAAM,MAAM,EAAE,MAab,CAAA;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"prettier-plugin.d.ts","sourceRoot":"","sources":["../src/prettier-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,QAAA,MAAM,MAAM,EAAE,MAab,CAAA;AAED,eAAe,MAAM,CAAA"}
@@ -1,8 +1,7 @@
1
1
  import { dirname } from "node:path";
2
2
  import * as prettierMarkdown from "prettier/plugins/markdown";
3
3
  import { cosmiconfig } from "cosmiconfig";
4
- import { HTML_ONLY_OPTION_KEYS } from "./html.js";
5
- import { MARKDOWN_ONLY_OPTION_KEYS } from "./markdown.js";
4
+ import { HTML_ONLY_OPTION_KEYS, MARKDOWN_ONLY_OPTION_KEYS } from "./sink-option-keys.js";
6
5
  import { remarkPunctilio } from "./remark.js";
7
6
  import { omitKeys } from "./utils.js";
8
7
  const upstreamMarkdown = prettierMarkdown.parsers.markdown;
@@ -1 +1 @@
1
- {"version":3,"file":"prettier-plugin.js","sourceRoot":"","sources":["../src/prettier-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,KAAK,gBAAgB,MAAM,2BAA2B,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AACzD,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAwB,CAAA;AAC1E,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;AAEzC,0EAA0E;AAC1E,4EAA4E;AAC5E,gEAAgE;AAChE,MAAM,wBAAwB,GAAsB;IAClD,GAAG,yBAAyB;IAC5B,GAAG,qBAAqB;CACzB,CAAA;AAED,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IACnD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACxC,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAgC,EAAE,wBAAwB,CAAC,CAAA;AACpF,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAA4B,EAAE,GAAS;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,4DAA4D;IAC5D,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAyC,CAAA;IACjF,MAAM,WAAW,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,MAAM,GAAW;IACrB,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,GAAG,gBAAgB;YACnB,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO;gBACvB,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;gBACvD,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAA;gBACnG,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAA;gBAClD,MAAM,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;gBACnC,OAAO,GAAG,CAAA;YACZ,CAAC;SACF;KACF;CACF,CAAA;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"prettier-plugin.js","sourceRoot":"","sources":["../src/prettier-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,KAAK,gBAAgB,MAAM,2BAA2B,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AACxF,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAwB,CAAA;AAC1E,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;AAEzC,0EAA0E;AAC1E,4EAA4E;AAC5E,gEAAgE;AAChE,MAAM,wBAAwB,GAAsB;IAClD,GAAG,yBAAyB;IAC5B,GAAG,qBAAqB;CACzB,CAAA;AAED,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IACnD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACxC,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAgC,EAAE,wBAAwB,CAAC,CAAA;AACpF,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAA4B,EAAE,GAAS;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,4DAA4D;IAC5D,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAyC,CAAA;IACjF,MAAM,WAAW,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,MAAM,GAAW;IACrB,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,GAAG,gBAAgB;YACnB,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO;gBACvB,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;gBACvD,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAA;gBACnG,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAA;gBAClD,MAAM,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;gBACnC,OAAO,GAAG,CAAA;YACZ,CAAC;SACF;KACF;CACF,CAAA;AAED,eAAe,MAAM,CAAA"}
package/dist/utils.d.ts CHANGED
@@ -7,6 +7,11 @@
7
7
  export declare function formatErrorString(content: string, label: string): string;
8
8
  /** Throws if `options` contains a key not present in `validKeys`. @internal */
9
9
  export declare function assertKnownOptionKeys(options: object, validKeys: readonly string[], context: string): void;
10
+ /**
11
+ * Runs `transform` on `input` without its leading BOM, which would otherwise
12
+ * be parsed as content, and restores the BOM on the result. @internal
13
+ */
14
+ export declare function transformPreservingBom(input: string, transform: (inputWithoutBom: string) => Promise<string>): Promise<string>;
10
15
  /** Returns a copy of `obj` without the listed keys. @internal */
11
16
  export declare function omitKeys<T extends object>(obj: T, keys: readonly string[]): Partial<T>;
12
17
  /** Returns a copy of `obj` with `undefined`-valued keys removed. @internal */
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,EAAE,MAAM,GACd,IAAI,CAQN;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAMtF;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMpE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,EAAE,MAAM,GACd,IAAI,CAQN;AAID;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACtD,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAMtF;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMpE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD"}
package/dist/utils.js CHANGED
@@ -29,6 +29,16 @@ export function assertKnownOptionKeys(options, validKeys, context) {
29
29
  }
30
30
  }
31
31
  }
32
+ const BYTE_ORDER_MARK = "\uFEFF";
33
+ /**
34
+ * Runs `transform` on `input` without its leading BOM, which would otherwise
35
+ * be parsed as content, and restores the BOM on the result. @internal
36
+ */
37
+ export async function transformPreservingBom(input, transform) {
38
+ if (!input.startsWith(BYTE_ORDER_MARK))
39
+ return transform(input);
40
+ return BYTE_ORDER_MARK + (await transform(input.slice(BYTE_ORDER_MARK.length)));
41
+ }
32
42
  /** Returns a copy of `obj` without the listed keys. @internal */
33
43
  export function omitKeys(obj, keys) {
34
44
  const result = {};
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "punctilio",
3
- "version": "5.4.8",
3
+ "version": "5.4.9",
4
4
  "description": "Smart typography transformations: curly quotes, em-dashes, en-dashes, and more",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -150,6 +150,7 @@
150
150
  "dependencies": {
151
151
  "cosmiconfig": "9.0.1",
152
152
  "ignore": "7.0.5",
153
+ "parse5": "7.3.0",
153
154
  "quick-lru": "7.3.0",
154
155
  "tinyglobby": "0.2.16",
155
156
  "unist-util-visit-parents": "6.0.2"
package/src/cli.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { createHash } from "node:crypto"
4
4
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs"
5
- import { readFile, rename, writeFile } from "node:fs/promises"
5
+ import { chmod, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises"
6
6
  import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path"
7
7
  import { fileURLToPath } from "node:url"
8
8
  import { parseArgs } from "node:util"
@@ -11,9 +11,10 @@ import { cosmiconfig } from "cosmiconfig"
11
11
  import ignore, { type Ignore } from "ignore"
12
12
  import { glob } from "tinyglobby"
13
13
 
14
+ import { HTML_ONLY_OPTION_KEYS, MARKDOWN_ONLY_OPTION_KEYS } from "./sink-option-keys.js"
14
15
  import type { MarkdownOptions } from "./markdown.js"
15
16
  import type { HtmlOptions } from "./html.js"
16
- import { HTML_ONLY_OPTION_KEYS, HTML_OPTION_KEYS, MARKDOWN_ONLY_OPTION_KEYS, MARKDOWN_OPTION_KEYS } from "./sink-option-keys.js"
17
+ import { loadSinkTransform, MissingPeerDependencyError, type PeerImporter } from "./peer-dependencies.js"
17
18
  import { PUNCTUATION_STYLES, type PunctuationStyle } from "./quotes.js"
18
19
  import { DASH_STYLES, type DashStyle } from "./dashes.js"
19
20
  import { assertKnownOptionKeys, omitKeys, stableStringify } from "./utils.js"
@@ -25,6 +26,8 @@ interface CliIO {
25
26
  stdin: AsyncIterable<string | Buffer>
26
27
  stdout: NodeJS.WritableStream
27
28
  stderr: NodeJS.WritableStream
29
+ /** Overrides how optional peer dependencies are imported; tests use it to simulate one being absent. */
30
+ importPeer?: PeerImporter
28
31
  }
29
32
 
30
33
  class UsageError extends Error {}
@@ -133,7 +136,7 @@ Options:
133
136
  --rule-marker <char> markdown thematic break character (choices: ${RULE_MARKERS.join(", ")})
134
137
  --skip-tag <tag> HTML tag whose contents are left alone (repeatable)
135
138
  --skip-class <class> HTML class name whose elements are skipped (repeatable)
136
- --no-fragment parse input as a full HTML document
139
+ --no-fragment always parse HTML as a full document (default: a full document is detected from a leading <!doctype> or <html>)
137
140
  -h, --help display help for command
138
141
  `
139
142
 
@@ -230,11 +233,74 @@ function inferFileType(path: string, override?: FileType): FileType {
230
233
  )
231
234
  }
232
235
 
236
+ interface ConfigValueRule {
237
+ expected: string
238
+ isValid: (value: unknown) => boolean
239
+ }
240
+
241
+ const BOOLEAN_RULE: ConfigValueRule = {
242
+ expected: "a boolean",
243
+ isValid: (value) => typeof value === "boolean",
244
+ }
245
+ const STRING_ARRAY_RULE: ConfigValueRule = {
246
+ expected: "an array of strings",
247
+ isValid: (value) => Array.isArray(value) && value.every((item) => typeof item === "string"),
248
+ }
249
+ const FUNCTION_RULE: ConfigValueRule = {
250
+ expected: "a function",
251
+ isValid: (value) => typeof value === "function",
252
+ }
253
+
254
+ function oneOfRule(choices: readonly string[]): ConfigValueRule {
255
+ return {
256
+ expected: `one of ${choices.join(", ")}`,
257
+ isValid: (value) => typeof value === "string" && choices.includes(value),
258
+ }
259
+ }
260
+
261
+ // `satisfies` makes the compiler flag any option key that lacks a rule.
262
+ const CONFIG_VALUE_RULES = {
263
+ symbols: BOOLEAN_RULE,
264
+ collapseSpaces: BOOLEAN_RULE,
265
+ includeArrows: BOOLEAN_RULE,
266
+ fractions: BOOLEAN_RULE,
267
+ degrees: BOOLEAN_RULE,
268
+ superscript: BOOLEAN_RULE,
269
+ ligatures: BOOLEAN_RULE,
270
+ nbsp: BOOLEAN_RULE,
271
+ fragment: BOOLEAN_RULE,
272
+ transformAllElements: BOOLEAN_RULE,
273
+ punctuationStyle: oneOfRule(PUNCTUATION_STYLES),
274
+ dashStyle: oneOfRule(DASH_STYLES),
275
+ emphasisMarker: oneOfRule(EMPHASIS_MARKERS),
276
+ strongMarker: oneOfRule(EMPHASIS_MARKERS),
277
+ bulletMarker: oneOfRule(BULLET_MARKERS),
278
+ ruleMarker: oneOfRule(RULE_MARKERS),
279
+ skipTags: STRING_ARRAY_RULE,
280
+ skipClasses: STRING_ARRAY_RULE,
281
+ shouldSkipText: FUNCTION_RULE,
282
+ } as const satisfies Record<keyof CliOptions, ConfigValueRule>
283
+
233
284
  // A config file may legitimately mix markdown-only and HTML-only keys for use
234
285
  // across file types; transformContent strips the keys the sink doesn't accept.
235
- const CLI_CONFIG_KEYS: readonly string[] = [
236
- ...new Set([...MARKDOWN_OPTION_KEYS, ...HTML_OPTION_KEYS]),
237
- ]
286
+ const CLI_CONFIG_KEYS: readonly string[] = Object.keys(CONFIG_VALUE_RULES)
287
+
288
+ // Keys are checked first, so every key present has a rule.
289
+ function assertValidConfig(config: unknown, source: string): asserts config is CliOptions {
290
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
291
+ throw new UsageError(`${source} must contain an object of options.`)
292
+ }
293
+ assertKnownOptionKeys(config, CLI_CONFIG_KEYS, source)
294
+ for (const [key, value] of Object.entries(config)) {
295
+ if (value === undefined) continue
296
+ const rule: ConfigValueRule = CONFIG_VALUE_RULES[key as keyof CliOptions]
297
+ if (!rule.isValid(value)) {
298
+ throw new UsageError(
299
+ `Invalid value for "${key}" in ${source}: expected ${rule.expected}, got ${JSON.stringify(value)}.`,
300
+ )
301
+ }
302
+ }
303
+ }
238
304
 
239
305
  // Config keys must match library option names (punctuationStyle, skipTags, …),
240
306
  // not the kebab-cased CLI flag names.
@@ -248,11 +314,11 @@ async function loadConfig(
248
314
  const result = configPath ? await explorer.load(configPath) : await explorer.search(cwd)
249
315
  if (!result || result.isEmpty) return {}
250
316
  try {
251
- assertKnownOptionKeys(result.config as object, CLI_CONFIG_KEYS, `config file ${result.filepath}`)
317
+ assertValidConfig(result.config, `config file ${result.filepath}`)
252
318
  } catch (err) {
253
- throw new UsageError((err as Error).message)
319
+ throw err instanceof UsageError ? err : new UsageError((err as Error).message)
254
320
  }
255
- return result.config as CliOptions
321
+ return result.config
256
322
  }
257
323
 
258
324
  function loadIgnore(cwd: string, ignorePath: string | undefined): Ignore {
@@ -273,9 +339,20 @@ function isLiteralPath(pattern: string, cwd: string): boolean {
273
339
  return !hasGlobMetacharacters(pattern) || existsSync(resolve(cwd, pattern))
274
340
  }
275
341
 
276
- // Literal positionals pass through untouched so they error loudly if missing and
277
- // bypass ignore rules (naming a file explicitly is an intent to format it).
278
- // Ignore rules and the zero-match warning apply only to glob-expanded paths.
342
+ // Dependency and VCS trees are never project sources; glob expansion skips them.
343
+ const DEFAULT_GLOB_IGNORES = ["**/node_modules/**", "**/.git/**"]
344
+
345
+ function isIgnored(file: string, cwd: string, ig: Ignore): boolean {
346
+ const rel = relative(cwd, file)
347
+ return !rel.startsWith("..") && ig.ignores(rel)
348
+ }
349
+
350
+ // Literal positionals pass through to the read step so a missing file errors
351
+ // loudly. Ignore rules apply to literals and glob expansions alike, because
352
+ // tools like pre-commit pass every staged file by name. Each glob is expanded
353
+ // on its own, so one that matches nothing is a usage error even when another
354
+ // glob matches; one whose matches are all ignored only warns. Negated globs
355
+ // (`!pattern`) exclude from every other glob rather than matching on their own.
279
356
  async function discoverFiles(
280
357
  patterns: string[],
281
358
  cwd: string,
@@ -284,17 +361,33 @@ async function discoverFiles(
284
361
  ): Promise<string[]> {
285
362
  const literals = patterns.filter((p) => isLiteralPath(p, cwd))
286
363
  const globs = patterns.filter((p) => !isLiteralPath(p, cwd))
287
- const expanded = globs.length > 0
288
- ? await glob(globs, { cwd, absolute: true, onlyFiles: true })
289
- : []
290
- const kept = expanded.filter((file) => {
291
- const rel = relative(cwd, file)
292
- return rel.startsWith("..") || !ig.ignores(rel)
293
- })
294
- if (globs.length > 0 && kept.length === 0) {
295
- stderr.write(`Warning: no files matched: ${globs.join(" ")}\n`)
364
+ const negatedGlobs = globs.filter((pattern) => pattern.startsWith("!"))
365
+ const positiveGlobs = globs.filter((pattern) => !pattern.startsWith("!"))
366
+ const expansions = await Promise.all(
367
+ positiveGlobs.map(async (pattern) => ({
368
+ pattern,
369
+ files: await glob([pattern, ...negatedGlobs], {
370
+ cwd,
371
+ absolute: true,
372
+ onlyFiles: true,
373
+ ignore: DEFAULT_GLOB_IGNORES,
374
+ }),
375
+ })),
376
+ )
377
+ const unmatched = positiveGlobs.length === 0
378
+ ? globs
379
+ : expansions.filter(({ files }) => files.length === 0).map(({ pattern }) => pattern)
380
+ if (unmatched.length > 0) {
381
+ throw new UsageError(`no files matched: ${unmatched.join(" ")}`)
296
382
  }
297
- return [...new Set([...literals.map((p) => resolve(cwd, p)), ...kept])]
383
+ const keptExpanded: string[] = []
384
+ for (const { pattern, files } of expansions) {
385
+ const kept = files.filter((file) => !isIgnored(file, cwd, ig))
386
+ if (kept.length === 0) stderr.write(`Warning: every file matched by ${pattern} is ignored\n`)
387
+ keptExpanded.push(...kept)
388
+ }
389
+ const keptLiterals = literals.map((p) => resolve(cwd, p)).filter((file) => !isIgnored(file, cwd, ig))
390
+ return [...new Set([...keptLiterals, ...keptExpanded])]
298
391
  }
299
392
 
300
393
  interface CacheEntry {
@@ -347,10 +440,20 @@ function saveCache(location: string, cache: Cache): void {
347
440
 
348
441
  // Replace a file's contents atomically: write a sibling temp file, then rename
349
442
  // it over the target so an interrupted run can never leave a half-written file.
350
- async function atomicWriteFile(path: string, contents: string): Promise<void> {
351
- const tmpPath = `${path}.${process.pid}.tmp`
352
- await writeFile(tmpPath, contents)
353
- await rename(tmpPath, path)
443
+ // The temp file replaces the symlink's target (not the link itself) and takes
444
+ // over the original file's permission bits; it is removed if anything fails.
445
+ export async function atomicWriteFile(path: string, contents: string): Promise<void> {
446
+ const targetPath = await realpath(path)
447
+ const { mode } = await stat(targetPath)
448
+ const tmpPath = `${targetPath}.${process.pid}.tmp`
449
+ try {
450
+ await writeFile(tmpPath, contents, { mode: mode & 0o7777 })
451
+ await chmod(tmpPath, mode & 0o7777)
452
+ await rename(tmpPath, targetPath)
453
+ } catch (err) {
454
+ await rm(tmpPath, { force: true })
455
+ throw err
456
+ }
354
457
  }
355
458
 
356
459
  // cwd-relative so the cache survives directory moves and cross-machine syncs.
@@ -382,30 +485,42 @@ function buildOptions(flags: ParsedFlags): CliOptions {
382
485
 
383
486
  // The merged options may carry keys for the other sink (e.g. emphasisMarker
384
487
  // alongside skipTags from one shared config); strip the inapplicable ones so
385
- // each sink's strict key validation only sees keys it understands. The sinks
386
- // load lazily because they import the optional unified peers, which --help,
387
- // --version and usage errors must not need.
388
- async function transformContent(input: string, type: FileType, opts: CliOptions): Promise<string> {
488
+ // each sink's strict key validation only sees keys it understands.
489
+ async function transformContent(
490
+ input: string,
491
+ type: FileType,
492
+ opts: CliOptions,
493
+ importPeer: PeerImporter | undefined,
494
+ ): Promise<string> {
389
495
  if (type === "md") {
390
- const { transformMarkdown } = await import("./markdown.js")
496
+ const transformMarkdown = await loadSinkTransform("md", importPeer)
391
497
  return transformMarkdown(input, omitKeys(opts, HTML_ONLY_OPTION_KEYS) as MarkdownOptions)
392
498
  }
393
- const { transformHtml } = await import("./html.js")
499
+ const transformHtml = await loadSinkTransform("html", importPeer)
394
500
  return transformHtml(input, omitKeys(opts, MARKDOWN_ONLY_OPTION_KEYS) as HtmlOptions)
395
501
  }
396
502
 
397
- // The unified pipelines emit LF-only output and unconditionally append a
398
- // trailing newline. Restore the source's line-ending style and trailing-newline
399
- // presence so a CRLF file doesn't gain mixed endings (which would make --check
400
- // report a change forever and --write churn on every run).
503
+ async function formatDocument(
504
+ original: string,
505
+ type: FileType,
506
+ opts: CliOptions,
507
+ importPeer: PeerImporter | undefined,
508
+ ): Promise<string> {
509
+ return matchLineEndings(original, await transformContent(original, type, opts, importPeer))
510
+ }
511
+
512
+ // The unified pipelines emit LF-only output. Markdown always ends in a newline;
513
+ // a full HTML document may end without one (its final newline can be dropped
514
+ // or sit inside <body>). Restore the source's line-ending style and
515
+ // trailing-newline presence so a CRLF file doesn't gain mixed endings (which
516
+ // would make --check report a change forever and --write churn on every run).
401
517
  function matchLineEndings(original: string, formatted: string): string {
402
- // Normalize the LF output back to CRLF when the source used it.
403
- let out = original.includes("\r\n")
518
+ const lineEnding = original.includes("\r\n") ? "\r\n" : "\n"
519
+ const out = lineEnding === "\r\n"
404
520
  ? formatted.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n")
405
521
  : formatted
406
- // Drop the appended trailing newline if the original had none.
407
- if (!/\n$/.test(original)) out = out.replace(/\r?\n$/, "")
408
- return out
522
+ if (!original.endsWith("\n")) return out.replace(/\r?\n$/, "")
523
+ return out.endsWith("\n") ? out : out + lineEnding
409
524
  }
410
525
 
411
526
  async function readStdin(stdin: AsyncIterable<string | Buffer>): Promise<string> {
@@ -443,7 +558,7 @@ export async function runCli(args: string[], io: CliIO): Promise<number> {
443
558
  }
444
559
  return await runValidated(buildFlags(values, tokens), positionals, io, version)
445
560
  } catch (err) {
446
- if (err instanceof UsageError) {
561
+ if (err instanceof UsageError || err instanceof MissingPeerDependencyError) {
447
562
  io.stderr.write(`Error: ${err.message}\n`)
448
563
  return 2
449
564
  }
@@ -475,6 +590,10 @@ async function runValidated(
475
590
  throw new UsageError("--write and --check are mutually exclusive.")
476
591
  }
477
592
 
593
+ if (flags.stdinFilepath !== undefined && positionals.some((p) => p !== "-")) {
594
+ throw new UsageError("--stdin-filepath cannot be combined with file arguments.")
595
+ }
596
+
478
597
  if (readsStdin(positionals, flags)) {
479
598
  if (write) {
480
599
  throw new UsageError("--write cannot be used when reading from stdin.")
@@ -489,7 +608,7 @@ async function runValidated(
489
608
  throw new UsageError("Reading from stdin requires --type md|html or --stdin-filepath <path>.")
490
609
  }
491
610
  const input = await readStdin(io.stdin)
492
- const output = matchLineEndings(input, await transformContent(input, type, opts))
611
+ const output = await formatDocument(input, type, opts, io.importPeer)
493
612
  if (check) return input === output ? 0 : 1
494
613
  io.stdout.write(output)
495
614
  return 0
@@ -502,6 +621,9 @@ async function runValidated(
502
621
 
503
622
  const ig = loadIgnore(cwd, flags.ignorePath)
504
623
  const files = await discoverFiles(positionals, cwd, ig, io.stderr)
624
+ // Resolve every file's type up front so an unsupported extension fails
625
+ // before any file has been rewritten.
626
+ const types = files.map((file) => inferFileType(file, flags.type))
505
627
 
506
628
  // In stdout mode every file's content must be produced, so a cache hit
507
629
  // could never skip work; only --write and --check use the cache.
@@ -512,8 +634,8 @@ async function runValidated(
512
634
  const optionsHash = cache ? hashOptions(opts) : ""
513
635
 
514
636
  let anyChanged = false
515
- for (const path of files) {
516
- const type = inferFileType(path, flags.type)
637
+ for (const [index, path] of files.entries()) {
638
+ const type = types[index]
517
639
  const original = await readFile(path, "utf8")
518
640
  const key = cache ? cacheKey(path, cwd) : ""
519
641
  if (cache) {
@@ -527,7 +649,7 @@ async function runValidated(
527
649
  ) continue
528
650
  }
529
651
 
530
- const formatted = matchLineEndings(original, await transformContent(original, type, opts))
652
+ const formatted = await formatDocument(original, type, opts, io.importPeer)
531
653
  if (!write && !check) {
532
654
  io.stdout.write(formatted)
533
655
  continue
@@ -551,9 +673,19 @@ async function runValidated(
551
673
  return check && anyChanged ? 1 : 0
552
674
  }
553
675
 
676
+ // npm links bins through symlinks, so argv[1] is the link, not the module file.
677
+ export function isMainModule(moduleUrl: string, entryPath: string | undefined): boolean {
678
+ if (entryPath === undefined) return false
679
+ try {
680
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(entryPath)
681
+ } catch (err) {
682
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return false
683
+ throw err
684
+ }
685
+ }
686
+
554
687
  /* istanbul ignore next -- entry-point bootstrap; logic is covered via runCli */
555
- // npm runs the bin through a node_modules/.bin symlink, so compare real paths.
556
- if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) {
688
+ if (isMainModule(import.meta.url, process.argv[1])) {
557
689
  runCli(process.argv.slice(2), {
558
690
  stdin: process.stdin,
559
691
  stdout: process.stdout,