@jterrazz/typescript 9.3.0 → 10.1.0
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/README.md +20 -16
- package/bin/commands/check.sh +387 -146
- package/bin/find-tsc.sh +30 -0
- package/bin/typescript.sh +79 -1
- package/lib/check-architecture.js +89 -0
- package/lib/check-baseline.js +144 -0
- package/lib/check-docs.js +4 -3
- package/lib/check-drift.js +209 -0
- package/lib/check-gitignore.js +4 -4
- package/lib/check-markdown.js +279 -0
- package/lib/check-names.js +125 -0
- package/lib/check-publish.js +150 -0
- package/lib/check-secrets.js +115 -0
- package/lib/check-suppressions.js +355 -0
- package/lib/doctor.js +185 -0
- package/lib/entry-points.js +91 -0
- package/lib/merge-knip-config.js +57 -25
- package/lib/tracked-files.js +165 -0
- package/lib/unsafe-fixers.js +25 -0
- package/lib/workspace-members.js +5 -6
- package/package.json +36 -13
- package/presets/oxfmt/index.js +49 -5
- package/presets/oxlint/profiles/astro.js +10 -0
- package/presets/oxlint/profiles/bun.js +7 -0
- package/presets/oxlint/profiles/expo.js +7 -0
- package/presets/oxlint/profiles/library.js +16 -0
- package/presets/oxlint/profiles/next.js +7 -0
- package/presets/oxlint/profiles/node.js +7 -0
- package/presets/oxlint/profiles/react.js +7 -0
- package/presets/prettier/astro.json +6 -0
- package/presets/tsconfig/astro.json +25 -0
- package/presets/tsconfig/expo.json +16 -6
- package/presets/tsconfig/library.json +17 -0
- package/presets/tsconfig/next.json +12 -2
- package/presets/tsconfig/node.json +18 -4
- package/presets/tsconfig/react.json +33 -0
- package/presets/tsdown/build.d.ts +13 -0
- package/presets/tsdown/bundle.d.ts +13 -0
- package/presets/tsdown/bundle.js +10 -1
- package/rules/README.md +23 -0
- package/rules/_contract.js +207 -0
- package/rules/_contract.test.ts +81 -0
- package/rules/a11y.js +51 -0
- package/rules/architecture/hexagonal.js +56 -0
- package/rules/architecture/layers.js +75 -0
- package/rules/astro.js +56 -0
- package/rules/bundler.js +19 -0
- package/rules/catalog.js +166 -0
- package/rules/catalog.test.ts +98 -0
- package/rules/compile.js +125 -0
- package/rules/core/eslint.js +234 -0
- package/rules/core/import.js +117 -0
- package/rules/core/jsdoc.js +52 -0
- package/rules/core/node.js +36 -0
- package/rules/core/oxc.js +54 -0
- package/rules/core/promise.js +39 -0
- package/rules/core/typescript.js +223 -0
- package/rules/core/unicorn.js +210 -0
- package/rules/next.js +53 -0
- package/rules/profiles.js +95 -0
- package/rules/react-native.js +48 -0
- package/rules/react.js +155 -0
- package/rules/sorted.js +41 -0
- package/rules/vitest.js +178 -0
- package/src/docs.d.ts +4 -4
- package/src/docs.js +75 -57
- package/src/docs.test.ts +43 -31
- package/src/index.d.ts +14 -9
- package/src/index.js +17 -8
- package/src/oxfmt.d.ts +15 -2
- package/src/oxfmt.test.ts +10 -0
- package/src/oxlint.d.ts +59 -10
- package/src/oxlint.js +36 -50
- package/src/oxlint.test.ts +82 -28
- package/presets/oxlint/architectures/hexagonal-rules.js +0 -39
- package/presets/oxlint/architectures/hexagonal.js +0 -13
- package/presets/oxlint/base.js +0 -145
- package/presets/oxlint/expo.js +0 -36
- package/presets/oxlint/next.js +0 -43
- package/presets/oxlint/node.js +0 -14
- package/presets/oxlint/plugins/codestyle.js +0 -231
package/bin/commands/check.sh
CHANGED
|
@@ -37,35 +37,67 @@ find_binary() {
|
|
|
37
37
|
# the per-platform @typescript/typescript-* packages instead of a second
|
|
38
38
|
# package named "typescript": typedoc and eslint-plugin-perfectionist load
|
|
39
39
|
# the JS API from the "typescript" name (v6 here), and any typescript@7 in
|
|
40
|
-
# the tree can hijack that lookup under pnpm's hoist fallback.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
# the tree can hijack that lookup under pnpm's hoist fallback. The lookup is
|
|
41
|
+
# shared with the CLI's `tsc` passthrough, so it lives in one file.
|
|
42
|
+
# shellcheck source=../find-tsc.sh
|
|
43
|
+
. "$SCRIPT_DIR/../find-tsc.sh"
|
|
44
|
+
|
|
45
|
+
# oxlint's type-aware rules run in `tsgolint`, a separate binary it looks up on
|
|
46
|
+
# PATH — and a consumer's PATH has no reason to carry this package's bin dir. It
|
|
47
|
+
# is a dependency here, so the lookup is made to succeed by putting the
|
|
48
|
+
# directory that holds it in front, for this process and its children only.
|
|
49
|
+
add_tsgolint_to_path() {
|
|
50
|
+
local tsgolint
|
|
51
|
+
tsgolint=$(find_binary tsgolint)
|
|
52
|
+
case "$tsgolint" in
|
|
53
|
+
*/*)
|
|
54
|
+
PATH="$(cd -P "$(dirname "$tsgolint")" && pwd):$PATH"
|
|
55
|
+
export PATH
|
|
56
|
+
;;
|
|
53
57
|
esac
|
|
54
|
-
|
|
55
|
-
local pkg="@typescript/typescript-$os-$arch"
|
|
56
|
-
if [ -x "$PACKAGE_ROOT/node_modules/$pkg/lib/tsc" ]; then
|
|
57
|
-
echo "$PACKAGE_ROOT/node_modules/$pkg/lib/tsc"
|
|
58
|
-
elif [ -x "$PACKAGE_ROOT/../../$pkg/lib/tsc" ]; then
|
|
59
|
-
echo "$PACKAGE_ROOT/../../$pkg/lib/tsc"
|
|
60
|
-
else
|
|
61
|
-
find_binary tsc
|
|
62
|
-
fi
|
|
63
58
|
}
|
|
64
59
|
|
|
60
|
+
add_tsgolint_to_path
|
|
61
|
+
|
|
62
|
+
# The config a consumer declares its rules in — the one thing that makes the
|
|
63
|
+
# drift report answerable. Without one there is no profile to have drifted from.
|
|
64
|
+
OXLINT_CONFIG=""
|
|
65
|
+
for candidate in oxlint.config.ts oxlint.config.mjs oxlint.config.js oxlint.config.cjs .oxlintrc.json; do
|
|
66
|
+
[ -f "$candidate" ] && { OXLINT_CONFIG="$candidate"; break; }
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# The ratchet's file, at the project root. Its presence is what turns the oxlint
|
|
70
|
+
# pass from "no diagnostic at all" into "no diagnostic above what was recorded".
|
|
71
|
+
BASELINE_FILE="oxlint.baseline.json"
|
|
72
|
+
|
|
65
73
|
TSC=$(find_tsc)
|
|
66
74
|
OXLINT=$(find_binary oxlint)
|
|
67
75
|
OXFMT=$(find_binary oxfmt)
|
|
68
76
|
KNIP=$(find_binary knip)
|
|
77
|
+
DEPCRUISE=$(find_binary depcruise)
|
|
78
|
+
PUBLINT=$(find_binary publint)
|
|
79
|
+
ATTW=$(find_binary attw)
|
|
80
|
+
PRETTIER=$(find_binary prettier)
|
|
81
|
+
|
|
82
|
+
# `astro` is the CONSUMER's dependency, never this package's: the pass runs its
|
|
83
|
+
# checker, it does not ship one. So the lookup starts at the project.
|
|
84
|
+
find_project_binary() {
|
|
85
|
+
local name="$1"
|
|
86
|
+
if [ -x "node_modules/.bin/$name" ]; then
|
|
87
|
+
echo "$PWD/node_modules/.bin/$name"
|
|
88
|
+
else
|
|
89
|
+
find_binary "$name"
|
|
90
|
+
fi
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# The one file shape oxfmt does not parse. The values are the oxfmt values —
|
|
94
|
+
# 100 / 4 / single / all — so a consumer never declares a formatter of its own.
|
|
95
|
+
#
|
|
96
|
+
# The plugin is passed as a RESOLVED PATH, not as a name in the config: prettier
|
|
97
|
+
# resolves a plugin name from the working directory, which is the consumer's,
|
|
98
|
+
# and the consumer is precisely the project that no longer declares it.
|
|
99
|
+
PRETTIER_ASTRO_CONFIG="$PACKAGE_ROOT/presets/prettier/astro.json"
|
|
100
|
+
PRETTIER_ASTRO_PLUGIN=$(cd "$PACKAGE_ROOT" && node -e 'process.stdout.write(require.resolve("prettier-plugin-astro"))' 2>/dev/null)
|
|
69
101
|
CHECKER=$(find_binary jterrazz-test-check)
|
|
70
102
|
|
|
71
103
|
# ── The unit is the workspace package, not the repository ────────────────────
|
|
@@ -106,15 +138,22 @@ project_uses_jterrazz_test() {
|
|
|
106
138
|
node -e 'const {readFileSync}=require("node:fs");const p=JSON.parse(readFileSync(process.argv[1],"utf8"));const d={...p.dependencies,...p.devDependencies,...p.peerDependencies};process.exit(d["@jterrazz/test"]?0:1)' "$dir/package.json" 2>/dev/null
|
|
107
139
|
}
|
|
108
140
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
local
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
141
|
+
# An Astro project, read off its manifest. `.astro` is the one file shape oxfmt
|
|
142
|
+
# does not parse and `astro check` is the only checker that reads a template's
|
|
143
|
+
# frontmatter, so the pass exists exactly where the dependency does.
|
|
144
|
+
project_uses_astro() {
|
|
145
|
+
local dir="${1:-.}"
|
|
146
|
+
[ -f "$dir/package.json" ] || return 1
|
|
147
|
+
node -e 'const {readFileSync}=require("node:fs");const p=JSON.parse(readFileSync(process.argv[1],"utf8"));const d={...p.dependencies,...p.devDependencies,...p.peerDependencies};process.exit(d["astro"]?0:1)' "$dir/package.json" 2>/dev/null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# A package the registry would accept: it names an entry (`exports`, `main`) or
|
|
151
|
+
# a publish target, and it never says it is private. A private package, and a
|
|
152
|
+
# workspace root that only holds members, have no tarball to be judged on.
|
|
153
|
+
project_is_publishable() {
|
|
154
|
+
local dir="${1:-.}"
|
|
155
|
+
[ -f "$dir/package.json" ] || return 1
|
|
156
|
+
node -e 'const {readFileSync}=require("node:fs");const p=JSON.parse(readFileSync(process.argv[1],"utf8"));process.exit(p.private!==true&&(p.exports||p.main||p.publishConfig)?0:1)' "$dir/package.json" 2>/dev/null
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
# A path git has been told to forget is not this workspace's source. Clones,
|
|
@@ -174,31 +213,21 @@ discover_docs_roots() {
|
|
|
174
213
|
} | LC_ALL=C sort -u
|
|
175
214
|
}
|
|
176
215
|
|
|
177
|
-
# The
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
local is_cjs=false
|
|
188
|
-
case "$cfg" in
|
|
189
|
-
*.cjs) is_cjs=true ;;
|
|
216
|
+
# The name of the oxlint config, when that config is CommonJS — and nothing
|
|
217
|
+
# otherwise. Everything this package ships is ESM, and so is every oxlint JS
|
|
218
|
+
# plugin the estate writes; a CommonJS config cannot load either, and oxlint
|
|
219
|
+
# drops what it cannot load and still exits 0. The rules a config names are the
|
|
220
|
+
# whole claim of a lint run, so the shape of the config is the oxlint pass's
|
|
221
|
+
# business ([Quality checks](../../docs/06-quality-checks.md)).
|
|
222
|
+
commonjs_oxlint_config() {
|
|
223
|
+
case "$OXLINT_CONFIG" in
|
|
224
|
+
*.cjs) printf '%s' "$OXLINT_CONFIG" ;;
|
|
190
225
|
*.js)
|
|
191
226
|
if ! node -e 'process.exit(require("./package.json").type==="module"?0:1)' 2>/dev/null; then
|
|
192
|
-
|
|
227
|
+
printf '%s' "$OXLINT_CONFIG"
|
|
193
228
|
fi
|
|
194
229
|
;;
|
|
195
230
|
esac
|
|
196
|
-
|
|
197
|
-
if [ "$is_cjs" = true ]; then
|
|
198
|
-
printf "${RED} WARNING ${NC} @jterrazz/test is installed but %s is CommonJS.\n" "$cfg"
|
|
199
|
-
printf " The @jterrazz/test oxlint plugin is ESM-only and will be SILENTLY DROPPED —\n"
|
|
200
|
-
printf " none of the jterrazz/* rules will run. Switch to an ESM config (oxlint.config.ts or .mjs).\n\n"
|
|
201
|
-
fi
|
|
202
231
|
}
|
|
203
232
|
|
|
204
233
|
# Parse command and args
|
|
@@ -226,6 +255,75 @@ while [[ $# -gt 0 ]]; do
|
|
|
226
255
|
esac
|
|
227
256
|
done
|
|
228
257
|
|
|
258
|
+
# ── How a pass speaks ────────────────────────────────────────────────────────
|
|
259
|
+
# Every pass that RAN prints the same three things, in the same order: a `RUN`
|
|
260
|
+
# header carrying its label, whatever it has to say, and one verdict line. A
|
|
261
|
+
# pass that did not apply — no Astro in the project, no declared layer map —
|
|
262
|
+
# prints nothing at all, because it answered no question.
|
|
263
|
+
#
|
|
264
|
+
# What sits between the header and the verdict is the one variable: a failing
|
|
265
|
+
# pass prints its whole captured log, and a passing one stays silent unless it
|
|
266
|
+
# WROTE something. `fix` changed a file the operator owns and silence would hide
|
|
267
|
+
# it, so a writer asks for its log with the fourth argument; a reader's success
|
|
268
|
+
# chatter — oxlint's summary, which its reporter prints on a runner and in a
|
|
269
|
+
# terminal and drops under an AI agent — never reaches the stream, so a green
|
|
270
|
+
# run is byte-identical everywhere.
|
|
271
|
+
report_pass() {
|
|
272
|
+
local label="$1" status="$2" log="$3" writer="${4:-}"
|
|
273
|
+
|
|
274
|
+
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} %s\n\n" "$label"
|
|
275
|
+
if [ "$status" -ne 0 ] || [ -n "$writer" ]; then
|
|
276
|
+
[ -s "$log" ] && cat "$log"
|
|
277
|
+
fi
|
|
278
|
+
if [ "$status" -eq 0 ]; then
|
|
279
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
280
|
+
else
|
|
281
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" "$status"
|
|
282
|
+
fi
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Two ways a lint run says nothing about the rules it was supposed to enforce.
|
|
286
|
+
#
|
|
287
|
+
# A config oxlint cannot parse — a `jsPlugins` naming a module that is not there
|
|
288
|
+
# is the common one — makes it print `Failed to parse oxlint configuration file`
|
|
289
|
+
# and lint nothing; 1.83.0 exits 1 for it, and this names the refusal in the
|
|
290
|
+
# toolchain's own vocabulary rather than leaving a reader with the tool's text.
|
|
291
|
+
# A CommonJS config is the silent one: oxlint drops it whole, prints NOTHING and
|
|
292
|
+
# exits 0, so the run is green having enforced no rule the config named.
|
|
293
|
+
#
|
|
294
|
+
# Both are the oxlint pass refusing, so both are written into its own log and
|
|
295
|
+
# both fail it. After this, `lint_status` is the verdict on what the linter
|
|
296
|
+
# actually ran, not on what it managed to exit with.
|
|
297
|
+
judge_lint_config() {
|
|
298
|
+
local cjs
|
|
299
|
+
cjs=$(commonjs_oxlint_config)
|
|
300
|
+
local refusals=""
|
|
301
|
+
|
|
302
|
+
if grep -q 'Failed to parse oxlint configuration file' "$tmp_dir/lint.log" 2>/dev/null; then
|
|
303
|
+
refusals+="oxlint-config-unparsed ${OXLINT_CONFIG} oxlint refused this config and linted nothing — its own report is above"$'\n'
|
|
304
|
+
fi
|
|
305
|
+
if [ -n "$cjs" ]; then
|
|
306
|
+
refusals+="oxlint-config-commonjs ${cjs} a CommonJS config cannot load an ESM preset or plugin, and oxlint drops what it cannot load — write oxlint.config.ts or .mjs"$'\n'
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
[ -z "$refusals" ] && return 0
|
|
310
|
+
|
|
311
|
+
printf '%s' "$refusals" >> "$tmp_dir/lint.log"
|
|
312
|
+
lint_status=1
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# One pass, N runs: the logs of the runs that FAILED, joined into the single log
|
|
316
|
+
# the pass reports under. A green member stays silent — it is the same pass.
|
|
317
|
+
join_logs() {
|
|
318
|
+
local into="$1"
|
|
319
|
+
shift
|
|
320
|
+
: > "$into"
|
|
321
|
+
local log
|
|
322
|
+
for log in "$@"; do
|
|
323
|
+
[ -s "$log" ] && cat "$log" >> "$into"
|
|
324
|
+
done
|
|
325
|
+
}
|
|
326
|
+
|
|
229
327
|
# Create a temporary directory for log files
|
|
230
328
|
tmp_dir=$(mktemp -d)
|
|
231
329
|
cleanup() { rm -rf "$tmp_dir"; }
|
|
@@ -243,27 +341,58 @@ run_checks() {
|
|
|
243
341
|
|
|
244
342
|
printf "${CYAN_BG}${BRIGHT_WHITE} START ${NC} ${LABEL}\n"
|
|
245
343
|
|
|
246
|
-
if workspace_uses_jterrazz_test; then
|
|
247
|
-
warn_cjs_oxlint_config
|
|
248
|
-
fi
|
|
249
|
-
|
|
250
344
|
# Run all tools in parallel
|
|
251
345
|
"$TSC" --noEmit > "$tmp_dir/type.log" 2>&1 &
|
|
252
346
|
local type_pid=$!
|
|
253
347
|
|
|
348
|
+
# --type-aware is explicit and unconditional: the rules it unlocks are the
|
|
349
|
+
# ones no syntactic linter can express, and a flag that is only sometimes
|
|
350
|
+
# passed is a rule set that is only sometimes enforced. `oxlint-tsgolint` is
|
|
351
|
+
# a dependency of this package, so it is there for every consumer.
|
|
352
|
+
#
|
|
353
|
+
# In FIX mode the two rewriters run one after the other, and they are the
|
|
354
|
+
# only pair that does: `oxlint --fix` and `oxfmt` write the same files, and
|
|
355
|
+
# in parallel the second writer lands its copy over the first's — after
|
|
356
|
+
# which `fix` then `check` fails on formatting the fix had just settled.
|
|
357
|
+
local lint_pid=""
|
|
358
|
+
local lint_status=0
|
|
359
|
+
local format_status=0
|
|
254
360
|
if [ "$FIX_MODE" = true ]; then
|
|
255
|
-
|
|
361
|
+
# A fixer that changes MEANING is never applied unattended: the rules
|
|
362
|
+
# marked `unsafe` in the manifest are allowed for THIS run only, so
|
|
363
|
+
# `fix` leaves them alone and `check` still reports them for a human.
|
|
364
|
+
local unsafe_fixers=()
|
|
365
|
+
while IFS= read -r flag; do
|
|
366
|
+
[ -n "$flag" ] && unsafe_fixers+=("$flag")
|
|
367
|
+
done < <(node "$PACKAGE_ROOT/lib/unsafe-fixers.js")
|
|
368
|
+
|
|
369
|
+
"$OXLINT" --type-aware --fix "${unsafe_fixers[@]}" "${LINT_ARGS[@]}" \
|
|
370
|
+
> "$tmp_dir/lint.log" 2>&1 ||
|
|
371
|
+
lint_status=$?
|
|
372
|
+
"$OXFMT" > "$tmp_dir/format.log" 2>&1 || format_status=$?
|
|
256
373
|
else
|
|
257
|
-
"$OXLINT" "${LINT_ARGS[@]}" > "$tmp_dir/lint.log" 2>&1 &
|
|
374
|
+
"$OXLINT" --type-aware "${LINT_ARGS[@]}" > "$tmp_dir/lint.log" 2>&1 &
|
|
375
|
+
lint_pid=$!
|
|
258
376
|
fi
|
|
259
|
-
local lint_pid=$!
|
|
260
377
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
378
|
+
# The same run, machine-readable, so the ratchet can be judged rule by rule.
|
|
379
|
+
# A second invocation rather than a reformat of the first: the human log is
|
|
380
|
+
# what a failing pass prints, and neither form can be derived from the other.
|
|
381
|
+
# In check mode it runs BESIDE the human one, on the same bytes. In fix mode
|
|
382
|
+
# it cannot: the bytes change under the fixer, so the machine-readable run
|
|
383
|
+
# is made after it, below.
|
|
384
|
+
local lint_json_pid=""
|
|
385
|
+
if [ "$FIX_MODE" = false ] && [ -f "$BASELINE_FILE" ]; then
|
|
386
|
+
"$OXLINT" --type-aware --format json "${LINT_ARGS[@]}" \
|
|
387
|
+
> "$tmp_dir/lint.json" 2>/dev/null &
|
|
388
|
+
lint_json_pid=$!
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
local format_pid=""
|
|
392
|
+
if [ "$FIX_MODE" = false ]; then
|
|
264
393
|
"$OXFMT" --check > "$tmp_dir/format.log" 2>&1 &
|
|
394
|
+
format_pid=$!
|
|
265
395
|
fi
|
|
266
|
-
local format_pid=$!
|
|
267
396
|
|
|
268
397
|
# Knip: only run in check mode (fix mode is destructive)
|
|
269
398
|
# Merge base config (from this package) with optional project-local knip.json.
|
|
@@ -329,6 +458,113 @@ run_checks() {
|
|
|
329
458
|
docs_layout_pid=$!
|
|
330
459
|
fi
|
|
331
460
|
|
|
461
|
+
# Suppressions (directives): every place the project told a checker to look
|
|
462
|
+
# away is spelled in this toolchain's vocabulary, carries its reason, and
|
|
463
|
+
# names a rule that is still live. It runs in BOTH modes — `--fix` settles
|
|
464
|
+
# the two spellings a machine can settle, and never invents a reason.
|
|
465
|
+
local suppressions_pid=""
|
|
466
|
+
local suppressions_status=0
|
|
467
|
+
local suppressions_fix=()
|
|
468
|
+
[ "$FIX_MODE" = true ] && suppressions_fix=(--fix)
|
|
469
|
+
node "$PACKAGE_ROOT/lib/check-suppressions.js" . --oxlint "$OXLINT" \
|
|
470
|
+
"${suppressions_fix[@]}" "${LINT_ARGS[@]}" > "$tmp_dir/suppressions.log" 2>&1 &
|
|
471
|
+
suppressions_pid=$!
|
|
472
|
+
|
|
473
|
+
# Markdown (prose): every tracked page's coordinates resolve, and its blocks
|
|
474
|
+
# breathe. Check-only — there is no rewrite that splits a paragraph into the
|
|
475
|
+
# two ideas it was carrying. It reads the same `--ignore-pattern` globs the
|
|
476
|
+
# linter received, so one flag answers for the whole run.
|
|
477
|
+
local markdown_pid=""
|
|
478
|
+
local markdown_status=0
|
|
479
|
+
if [ "$FIX_MODE" = false ]; then
|
|
480
|
+
node "$PACKAGE_ROOT/lib/check-markdown.js" . "${LINT_ARGS[@]}" \
|
|
481
|
+
> "$tmp_dir/markdown.log" 2>&1 &
|
|
482
|
+
markdown_pid=$!
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
# Names: what the project calls its own parts, under the roots where a
|
|
486
|
+
# project keeps what it wrote. Check-only — renaming a file is a move, and
|
|
487
|
+
# choosing the name it moves to is the work the rule is asking for.
|
|
488
|
+
local names_pid=""
|
|
489
|
+
local names_status=0
|
|
490
|
+
if [ "$FIX_MODE" = false ]; then
|
|
491
|
+
node "$PACKAGE_ROOT/lib/check-names.js" . "${LINT_ARGS[@]}" > "$tmp_dir/names.log" 2>&1 &
|
|
492
|
+
names_pid=$!
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# Secrets: no file the project would commit carries a live-looking
|
|
496
|
+
# credential. The gate always applies, so WHICH engine answers — gitleaks
|
|
497
|
+
# where the machine has it, the built-in patterns where it does not — is
|
|
498
|
+
# decided inside the script, not here.
|
|
499
|
+
local secrets_pid=""
|
|
500
|
+
local secrets_status=0
|
|
501
|
+
if [ "$FIX_MODE" = false ]; then
|
|
502
|
+
node "$PACKAGE_ROOT/lib/check-secrets.js" . "${LINT_ARGS[@]}" \
|
|
503
|
+
> "$tmp_dir/secrets.log" 2>&1 &
|
|
504
|
+
secrets_pid=$!
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
# Astro: the consumer's own checker, plus the formatter for the one file
|
|
508
|
+
# shape oxfmt does not parse. Both halves run in fix mode too — prettier
|
|
509
|
+
# writes there, and `astro check` is read-only wherever it runs.
|
|
510
|
+
local astro_pid=""
|
|
511
|
+
local astro_status=0
|
|
512
|
+
if project_uses_astro "."; then
|
|
513
|
+
ASTRO=$(find_project_binary astro)
|
|
514
|
+
# A subshell, so both halves run and both are reported — a template that
|
|
515
|
+
# does not type-check is not a reason to stay quiet about its shape.
|
|
516
|
+
# `local` has no meaning past the `&`, hence the plain names.
|
|
517
|
+
(
|
|
518
|
+
"$ASTRO" check
|
|
519
|
+
astro_check=$?
|
|
520
|
+
if [ "$FIX_MODE" = true ]; then
|
|
521
|
+
"$PRETTIER" --write --config "$PRETTIER_ASTRO_CONFIG" \
|
|
522
|
+
--plugin "$PRETTIER_ASTRO_PLUGIN" \
|
|
523
|
+
--no-error-on-unmatched-pattern "**/*.astro"
|
|
524
|
+
else
|
|
525
|
+
"$PRETTIER" --check --config "$PRETTIER_ASTRO_CONFIG" \
|
|
526
|
+
--plugin "$PRETTIER_ASTRO_PLUGIN" \
|
|
527
|
+
--no-error-on-unmatched-pattern "**/*.astro"
|
|
528
|
+
fi
|
|
529
|
+
astro_format=$?
|
|
530
|
+
[ $astro_check -eq 0 ] && [ $astro_format -eq 0 ]
|
|
531
|
+
) > "$tmp_dir/astro.log" 2>&1 &
|
|
532
|
+
astro_pid=$!
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
# Architecture (layer map): the graph a project declared, resolved. Bash
|
|
536
|
+
# asks the one question that decides whether the gate applies at all — is
|
|
537
|
+
# there a map — and the script decides what it says. Opt-in by the file's
|
|
538
|
+
# existence: a project with no declared architecture is not in breach of one.
|
|
539
|
+
local architecture_pid=""
|
|
540
|
+
local architecture_status=0
|
|
541
|
+
if [ "$FIX_MODE" = false ] &&
|
|
542
|
+
{ [ -f ".dependency-cruiser.cjs" ] || [ -f ".dependency-cruiser.js" ] ||
|
|
543
|
+
[ -f ".dependency-cruiser.mjs" ]; }; then
|
|
544
|
+
node "$PACKAGE_ROOT/lib/check-architecture.js" . --depcruise "$DEPCRUISE" \
|
|
545
|
+
> "$tmp_dir/architecture.log" 2>&1 &
|
|
546
|
+
architecture_pid=$!
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
# Publish (packaging): what a published package promises, held to what the
|
|
550
|
+
# tarball will contain. Once per package the registry would accept — the
|
|
551
|
+
# unit is the workspace package, and a private one has no tarball.
|
|
552
|
+
local publish_pids=()
|
|
553
|
+
local publish_logs=()
|
|
554
|
+
local publish_status=0
|
|
555
|
+
if [ "$FIX_MODE" = false ]; then
|
|
556
|
+
local publish_index=0
|
|
557
|
+
for publish_root in "." "${WORKSPACE_MEMBERS[@]}"; do
|
|
558
|
+
project_is_publishable "$publish_root" || continue
|
|
559
|
+
node "$PACKAGE_ROOT/lib/check-publish.js" "$publish_root" \
|
|
560
|
+
--publint "$PUBLINT" --attw "$ATTW" \
|
|
561
|
+
> "$tmp_dir/publish-$publish_index.log" 2>&1 &
|
|
562
|
+
publish_pids+=($!)
|
|
563
|
+
publish_logs+=("$tmp_dir/publish-$publish_index.log")
|
|
564
|
+
publish_index=$((publish_index + 1))
|
|
565
|
+
done
|
|
566
|
+
fi
|
|
567
|
+
|
|
332
568
|
# Conventions checker: only in check mode, once per specs root the workspace
|
|
333
569
|
# owns, gated by the package that OWNS that root — a member may depend on
|
|
334
570
|
# @jterrazz/test while the root does not, and the reverse.
|
|
@@ -370,11 +606,37 @@ run_checks() {
|
|
|
370
606
|
|
|
371
607
|
# Wait and collect statuses
|
|
372
608
|
wait $type_pid; local type_status=$?
|
|
373
|
-
wait $lint_pid;
|
|
374
|
-
|
|
609
|
+
[ -n "$lint_pid" ] && { wait $lint_pid; lint_status=$?; }
|
|
610
|
+
|
|
611
|
+
# The ratchet, where the project keeps one: the pass is judged by what the
|
|
612
|
+
# baseline tolerates, not by oxlint's exit code — in FIX mode as much as in
|
|
613
|
+
# check mode, or `fix` then `check` reads red then green on the same tree.
|
|
614
|
+
# Bash decides whether the file is there; the script decides what it says.
|
|
615
|
+
#
|
|
616
|
+
# What fix mode judges is what SURVIVED the rewrite, so its machine-readable
|
|
617
|
+
# run is made here, after the fixer, and in the foreground.
|
|
618
|
+
if [ -f "$BASELINE_FILE" ]; then
|
|
619
|
+
if [ -n "$lint_json_pid" ]; then
|
|
620
|
+
wait $lint_json_pid
|
|
621
|
+
else
|
|
622
|
+
"$OXLINT" --type-aware --format json "${LINT_ARGS[@]}" \
|
|
623
|
+
> "$tmp_dir/lint.json" 2>/dev/null
|
|
624
|
+
fi
|
|
625
|
+
node "$PACKAGE_ROOT/lib/check-baseline.js" "$tmp_dir/lint.json" . \
|
|
626
|
+
>> "$tmp_dir/lint.log" 2>&1
|
|
627
|
+
lint_status=$?
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
[ -n "$format_pid" ] && { wait $format_pid; format_status=$?; }
|
|
375
631
|
[ -n "$knip_pid" ] && { wait $knip_pid; knip_status=$?; }
|
|
376
632
|
[ -n "$gitignore_pid" ] && { wait $gitignore_pid; gitignore_status=$?; }
|
|
377
633
|
[ -n "$docs_layout_pid" ] && { wait $docs_layout_pid; docs_layout_status=$?; }
|
|
634
|
+
wait $suppressions_pid; suppressions_status=$?
|
|
635
|
+
[ -n "$markdown_pid" ] && { wait $markdown_pid; markdown_status=$?; }
|
|
636
|
+
[ -n "$architecture_pid" ] && { wait $architecture_pid; architecture_status=$?; }
|
|
637
|
+
[ -n "$astro_pid" ] && { wait $astro_pid; astro_status=$?; }
|
|
638
|
+
[ -n "$names_pid" ] && { wait $names_pid; names_status=$?; }
|
|
639
|
+
[ -n "$secrets_pid" ] && { wait $secrets_pid; secrets_status=$?; }
|
|
378
640
|
|
|
379
641
|
# One pass, N runs: the pass fails if any run failed, and only the logs of
|
|
380
642
|
# the runs that FAILED are printed — a green member stays silent.
|
|
@@ -388,6 +650,16 @@ run_checks() {
|
|
|
388
650
|
index=$((index + 1))
|
|
389
651
|
done
|
|
390
652
|
|
|
653
|
+
local publish_failed_logs=()
|
|
654
|
+
index=0
|
|
655
|
+
for pid in "${publish_pids[@]}"; do
|
|
656
|
+
if ! wait "$pid"; then
|
|
657
|
+
publish_status=1
|
|
658
|
+
publish_failed_logs+=("${publish_logs[$index]}")
|
|
659
|
+
fi
|
|
660
|
+
index=$((index + 1))
|
|
661
|
+
done
|
|
662
|
+
|
|
391
663
|
local docs_failed_logs=()
|
|
392
664
|
index=0
|
|
393
665
|
for pid in "${docs_pids[@]}"; do
|
|
@@ -398,92 +670,61 @@ run_checks() {
|
|
|
398
670
|
index=$((index + 1))
|
|
399
671
|
done
|
|
400
672
|
|
|
401
|
-
#
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
printf "${GREEN}✓ Passed${NC}\n"
|
|
410
|
-
fi
|
|
673
|
+
# ── The report ───────────────────────────────────────────────────────────
|
|
674
|
+
# One order, and it is the chapter's: the three tools, the artefact gate,
|
|
675
|
+
# knip, the conventions checker, the two Docs passes, then the gates a
|
|
676
|
+
# project opts into and the four that read its tree on every run. A pass
|
|
677
|
+
# that did not apply is absent; every pass that ran prints the same block.
|
|
678
|
+
join_logs "$tmp_dir/checker.log" "${checker_failed_logs[@]}"
|
|
679
|
+
join_logs "$tmp_dir/docs.log" "${docs_failed_logs[@]}"
|
|
680
|
+
join_logs "$tmp_dir/publish.log" "${publish_failed_logs[@]}"
|
|
411
681
|
|
|
412
682
|
local lint_label="Oxlint Check"
|
|
413
|
-
[ "$FIX_MODE" = true ] && lint_label="Oxlint Fix"
|
|
414
|
-
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} ${lint_label}\n\n"
|
|
415
|
-
if [ $lint_status -ne 0 ]; then
|
|
416
|
-
[ -s "$tmp_dir/lint.log" ] && cat "$tmp_dir/lint.log"
|
|
417
|
-
printf "${RED}✗ Failed with exit code %d${NC}\n" $lint_status
|
|
418
|
-
else
|
|
419
|
-
printf "${GREEN}✓ Passed${NC}\n"
|
|
420
|
-
fi
|
|
421
|
-
|
|
422
683
|
local format_label="Oxfmt Check"
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
else
|
|
429
|
-
printf "${GREEN}✓ Passed${NC}\n"
|
|
430
|
-
fi
|
|
431
|
-
|
|
432
|
-
# The one pass that speaks on success: a rewrite changed a file the operator
|
|
433
|
-
# owns, and silence would hide it. In check mode a green gate writes nothing,
|
|
434
|
-
# so the green output stays byte-identical with the others.
|
|
435
|
-
if [ -n "$gitignore_pid" ]; then
|
|
436
|
-
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} Gitignore (artefacts)\n\n"
|
|
437
|
-
[ -s "$tmp_dir/gitignore.log" ] && cat "$tmp_dir/gitignore.log"
|
|
438
|
-
if [ $gitignore_status -ne 0 ]; then
|
|
439
|
-
printf "${RED}✗ Failed with exit code %d${NC}\n" $gitignore_status
|
|
440
|
-
else
|
|
441
|
-
printf "${GREEN}✓ Passed${NC}\n"
|
|
442
|
-
fi
|
|
684
|
+
local write=""
|
|
685
|
+
if [ "$FIX_MODE" = true ]; then
|
|
686
|
+
lint_label="Oxlint Fix"
|
|
687
|
+
format_label="Oxfmt Format"
|
|
688
|
+
write="writer"
|
|
443
689
|
fi
|
|
444
690
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
printf "${RED}✗ Failed with exit code %d${NC}\n" $docs_status
|
|
483
|
-
else
|
|
484
|
-
printf "${GREEN}✓ Passed${NC}\n"
|
|
485
|
-
fi
|
|
486
|
-
fi
|
|
691
|
+
judge_lint_config
|
|
692
|
+
report_pass "TypeScript Check" $type_status "$tmp_dir/type.log"
|
|
693
|
+
report_pass "$lint_label" $lint_status "$tmp_dir/lint.log"
|
|
694
|
+
report_pass "$format_label" $format_status "$tmp_dir/format.log"
|
|
695
|
+
[ -n "$gitignore_pid" ] &&
|
|
696
|
+
report_pass "Gitignore (artefacts)" $gitignore_status "$tmp_dir/gitignore.log" writer
|
|
697
|
+
[ "$FIX_MODE" = false ] &&
|
|
698
|
+
report_pass "Knip (unused code)" $knip_status "$tmp_dir/knip.log"
|
|
699
|
+
[ ${#checker_pids[@]} -gt 0 ] &&
|
|
700
|
+
report_pass "Test Conventions (@jterrazz/test)" $checker_status "$tmp_dir/checker.log"
|
|
701
|
+
[ -n "$docs_layout_pid" ] &&
|
|
702
|
+
report_pass "Docs (layout)" $docs_layout_status "$tmp_dir/docs-layout.log"
|
|
703
|
+
[ ${#docs_pids[@]} -gt 0 ] &&
|
|
704
|
+
report_pass "Docs (sync)" $docs_status "$tmp_dir/docs.log"
|
|
705
|
+
[ ${#publish_pids[@]} -gt 0 ] &&
|
|
706
|
+
report_pass "Publish (packaging)" $publish_status "$tmp_dir/publish.log"
|
|
707
|
+
[ -n "$architecture_pid" ] &&
|
|
708
|
+
report_pass "Architecture (layer map)" $architecture_status "$tmp_dir/architecture.log"
|
|
709
|
+
[ -n "$astro_pid" ] &&
|
|
710
|
+
report_pass "Astro (check + format)" $astro_status "$tmp_dir/astro.log" "$write"
|
|
711
|
+
report_pass "Suppressions (directives)" $suppressions_status "$tmp_dir/suppressions.log" "$write"
|
|
712
|
+
[ -n "$markdown_pid" ] &&
|
|
713
|
+
report_pass "Markdown (prose)" $markdown_status "$tmp_dir/markdown.log"
|
|
714
|
+
[ -n "$names_pid" ] && report_pass "Names (tree)" $names_status "$tmp_dir/names.log"
|
|
715
|
+
[ -n "$secrets_pid" ] && report_pass "Secrets (credentials)" $secrets_status "$tmp_dir/secrets.log"
|
|
716
|
+
|
|
717
|
+
# Drift: the report, not a gate — how far this project stands from the
|
|
718
|
+
# profile it says it extends, in four numbers. It runs last and it speaks on
|
|
719
|
+
# every check, because the alternative is what the estate had: every
|
|
720
|
+
# repository quietly a little further from the shared rulebook, and nobody
|
|
721
|
+
# able to say by how much without opening every config. Only a rule turned
|
|
722
|
+
# off with no reason beside it actually fails the run.
|
|
723
|
+
local drift_status=0
|
|
724
|
+
if [ "$FIX_MODE" = false ] && [ -n "$OXLINT_CONFIG" ]; then
|
|
725
|
+
printf "\n${CYAN_BG}${BRIGHT_WHITE} DRIFT ${NC} Deviations from the profile\n\n"
|
|
726
|
+
node "$PACKAGE_ROOT/lib/check-drift.js" . --oxlint "$OXLINT" "${LINT_ARGS[@]}"
|
|
727
|
+
drift_status=$?
|
|
487
728
|
fi
|
|
488
729
|
|
|
489
730
|
# Summary
|
|
@@ -493,7 +734,7 @@ run_checks() {
|
|
|
493
734
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} END ${NC} Finalizing quality checks\n\n"
|
|
494
735
|
fi
|
|
495
736
|
|
|
496
|
-
if [ $type_status -eq 0 ] && [ $lint_status -eq 0 ] && [ $format_status -eq 0 ] && [ $knip_status -eq 0 ] && [ $gitignore_status -eq 0 ] && [ $checker_status -eq 0 ] && [ $docs_layout_status -eq 0 ] && [ $docs_status -eq 0 ]; then
|
|
737
|
+
if [ $type_status -eq 0 ] && [ $lint_status -eq 0 ] && [ $format_status -eq 0 ] && [ $knip_status -eq 0 ] && [ $gitignore_status -eq 0 ] && [ $checker_status -eq 0 ] && [ $docs_layout_status -eq 0 ] && [ $docs_status -eq 0 ] && [ $markdown_status -eq 0 ] && [ $names_status -eq 0 ] && [ $secrets_status -eq 0 ] && [ $suppressions_status -eq 0 ] && [ $publish_status -eq 0 ] && [ $architecture_status -eq 0 ] && [ $astro_status -eq 0 ] && [ $drift_status -eq 0 ]; then
|
|
497
738
|
printf "${GREEN}✓ All checks passed${NC}\n"
|
|
498
739
|
exit 0
|
|
499
740
|
else
|