Repository navigation
Expand file tree
/
Copy pathlint-staged.config.mjs
More file actions
121 lines (112 loc) · 5.21 KB
/
Copy pathlint-staged.config.mjs
File metadata and controls
121 lines (112 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Dynamic lint-staged config: routes each staged file to the right autofix tool
// at commit time instead of hardcoding an extension→tool map that could fall
// out of date. eslint and ember-template-lint cover a small, deliberate set of
// extensions; everything else is handed to prettier IF prettier itself can
// format it — `getFileInfo` is prettier's own source of truth, so new formats
// (or a prettier upgrade that adds one) are picked up automatically with no
// edit here. All routing goes through scripts/lint-autofix.mjs, which applies
// fixes, re-stages, and warns without ever blocking the commit.
import { getFileInfo } from 'prettier';
import { basename, dirname, extname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
// pnpm owns these files: it (re)writes them with its own serializer, so no
// lint tool usefully applies — prettier reformatting pnpm-workspace.yaml just
// fights pnpm's next rewrite, and pnpm-lock.yaml is prettier-ignored anyway.
// Drop them before routing so they're never scanned or handed to any tool.
const PNPM_OWNED_FILES = new Set(['pnpm-lock.yaml', 'pnpm-workspace.yaml']);
// Trees whose contents are copied in verbatim from another repo and must keep
// that repo's bytes. `.prettierignore` already covers them, but eslint applies
// prettier formatting through eslint-plugin-prettier and never consults
// `.prettierignore` — so without this skip, staging a copied `.gts` reformats
// it and drifts the copy from its source. Match on a path segment so absolute
// staged paths work.
const VERBATIM_COPIED_TREES = [
// Licensed FileDef sample files whose bytes must match the integrity
// manifest in the sibling SOURCES.md.
'/packages/experiments-realm/filedef-fixtures/samples/',
// Vendored third-party bundles and data in Pret UI; each directory's
// README.md records the upstream version and how the bytes were built.
'/packages/pretui/color/index.js',
'/packages/pretui/zxcvbn/index.js',
'/packages/pretui/sigpad/index.js',
'/packages/pretui/emoji-data/data.ts',
'/packages/pretui/gridstack/index.js',
'/packages/pretui/media-chrome/index.js',
'/packages/pretui/surfaces/canvas/index.js',
'/packages/pretui/surfaces/layout/index.js',
'/packages/pretui/cropper/index.js',
'/packages/pretui/model-viewer/index.js',
'/packages/pretui/photoswipe/index.js',
'/packages/pretui/photoswipe/style.ts',
'/packages/pretui/plot/index.js',
'/packages/pretui/qrcode/index.js',
'/packages/pretui/wavesurfer/index.js',
'/packages/pretui/surfaces/grid/index.js',
];
const isVerbatimCopy = (file) => {
const posix = file.replace(/\\/g, '/');
return VERBATIM_COPIED_TREES.some((tree) => posix.includes(tree));
};
// Resolve .prettierignore next to this config (repo root) rather than relying
// on cwd, so getFileInfo's `ignored` verdict matches what the prettier CLI in
// lint-autofix.mjs actually honors.
const PRETTIER_IGNORE = join(
dirname(fileURLToPath(import.meta.url)),
'.prettierignore',
);
// eslint also applies prettier formatting for these via eslint-plugin-prettier,
// so they must NOT also go to prettier (that would double-process and fight
// over formatting). Matches what CI's `eslint .` lints.
const ESLINT_EXTENSIONS = new Set(['.js', '.ts', '.gjs', '.gts']);
// .gjs / .gts are routed to ember-template-lint in addition to eslint:
// eslint sees the surrounding JS/TS but can't lint the inline <template>
// blocks, so template-lint rules (e.g. no-unnecessary-curly-parens) would
// otherwise only fire in CI's `lint:hbs` (`ember-template-lint .`).
const TEMPLATE_EXTENSIONS = new Set(['.hbs', '.gjs', '.gts']);
const AUTOFIX = 'node scripts/lint-autofix.mjs';
// POSIX-safe single-quoting for paths embedded in the returned shell commands
// (staged paths can contain spaces).
const quote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
export default async (stagedFiles) => {
const eslintFiles = [];
const templateFiles = [];
const prettierFiles = [];
for (const file of stagedFiles) {
if (PNPM_OWNED_FILES.has(basename(file))) continue;
if (isVerbatimCopy(file)) continue;
const ext = extname(file);
let routed = false;
if (ESLINT_EXTENSIONS.has(ext)) {
eslintFiles.push(file);
routed = true;
}
if (TEMPLATE_EXTENSIONS.has(ext)) {
templateFiles.push(file);
routed = true;
}
if (!routed) {
// Ask prettier whether it can format this file. resolveConfig picks up
// the repo's .prettierrc (and its plugins) and ignorePath consults
// .prettierignore, so the decision matches what `prettier --write` would
// actually do — including skipping ignored files like pnpm-lock.yaml.
const { inferredParser, ignored } = await getFileInfo(file, {
resolveConfig: true,
ignorePath: PRETTIER_IGNORE,
});
if (inferredParser && !ignored) prettierFiles.push(file);
}
}
const commands = [];
if (eslintFiles.length) {
commands.push(`${AUTOFIX} eslint ${eslintFiles.map(quote).join(' ')}`);
}
if (templateFiles.length) {
commands.push(
`${AUTOFIX} ember-template-lint ${templateFiles.map(quote).join(' ')}`,
);
}
if (prettierFiles.length) {
commands.push(`${AUTOFIX} prettier ${prettierFiles.map(quote).join(' ')}`);
}
return commands;
};