pasika 0.5.17 → 0.6.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 +7 -7
- package/dist/eslint/pasika/index.d.ts +12 -5
- package/dist/eslint/pasika/index.js +370 -67
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -88,8 +88,8 @@ The script refuses a hash no requirement has, a `ref` naming a rule that does no
|
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
npm run build
|
|
91
|
-
npm run dogfood -- ../some/repo #
|
|
92
|
-
npm run dogfood -- ../some/repo --preset=
|
|
91
|
+
npm run dogfood -- ../some/repo # pasikaNextjsApp preset (default)
|
|
92
|
+
npm run dogfood -- ../some/repo --preset=pasikaApp
|
|
93
93
|
npm run dogfood -- ../some/repo --pasika-only # tally only pasika/* rules
|
|
94
94
|
npm run dogfood -- ../some/repo --rule=css-entry-point --findings
|
|
95
95
|
npm run dogfood -- ../some/repo --json # machine-readable report
|
|
@@ -115,18 +115,18 @@ const { eslintConfig } = styleguide({
|
|
|
115
115
|
export default eslintConfig;
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
For a plain TypeScript repository, enable `
|
|
118
|
+
For a plain TypeScript repository, enable `pasikaApp` instead of `pasikaNextjsApp`; that preset covers the manifest, the zirka contract, and the docs only — the `src/**` source rules belong to the Next.js app preset. `zirka` composes the pasika ruleset over four file scopes: TS/TSX under `src/**`, `globals.css` and other stylesheets, `package.json`, and markdown — each with its own ESLint language.
|
|
119
119
|
|
|
120
120
|
### Without Zirka
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
123
|
// eslint.config.ts — plain TypeScript repository
|
|
124
|
-
import {
|
|
124
|
+
import { pasikaApp } from "pasika/eslint";
|
|
125
125
|
|
|
126
|
-
export default
|
|
126
|
+
export default pasikaApp;
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
`
|
|
129
|
+
`pasikaApp` is the plain-TypeScript-repository preset — the package.json manifest, the zirka configuration contract, and the docs. It carries no `src/**` source block: source linting is the Next.js app's job. `pasikaNextjsApp` is the full framework preset: everything in `pasikaApp` plus the Next.js-stack manifest requirement, the `src/**` app source rules, and the Tailwind stylesheet blocks. The granular rule objects (`tailwindRules`, `repoPackageJsonRules`, `documentationRules`) stay exported for manual wiring.
|
|
130
130
|
|
|
131
131
|
The `src/**` blocks ship `@typescript-eslint/parser` themselves, so a standalone preset parses `.ts`/`.tsx` correctly on its own (`pasika` lists it as a runtime dependency).
|
|
132
132
|
|
|
@@ -231,7 +231,7 @@ Many of the TS/TSX rules call the TypeScript compiler API directly at lint time,
|
|
|
231
231
|
```bash
|
|
232
232
|
npm run lint
|
|
233
233
|
npm run typecheck
|
|
234
|
-
npm run test
|
|
234
|
+
npm run test:unit
|
|
235
235
|
npm run coverage
|
|
236
236
|
npm run build
|
|
237
237
|
npm run dogfood -- ../some/repo # requires a build; see Dogfooding above
|
|
@@ -119,6 +119,8 @@ declare const tailwindRules: {
|
|
|
119
119
|
declare const repoPackageJsonRules: {
|
|
120
120
|
"no-vulyk-dependency": _eslint_json.JSONRuleDefinition;
|
|
121
121
|
"exact-version": _eslint_json.JSONRuleDefinition;
|
|
122
|
+
"lint-setup": _eslint_json.JSONRuleDefinition;
|
|
123
|
+
"vitest-coverage": _eslint_json.JSONRuleDefinition;
|
|
122
124
|
};
|
|
123
125
|
/** Package.json rules specific to a Next.js (or React) application. */
|
|
124
126
|
declare const nextjsPackageJsonRules: {
|
|
@@ -147,6 +149,8 @@ declare const pasikaPlugin: {
|
|
|
147
149
|
"nextjs-stack": _eslint_json.JSONRuleDefinition;
|
|
148
150
|
"no-vulyk-dependency": _eslint_json.JSONRuleDefinition;
|
|
149
151
|
"exact-version": _eslint_json.JSONRuleDefinition;
|
|
152
|
+
"lint-setup": _eslint_json.JSONRuleDefinition;
|
|
153
|
+
"vitest-coverage": _eslint_json.JSONRuleDefinition;
|
|
150
154
|
"theme-reset": _eslint_css.CSSRuleDefinition;
|
|
151
155
|
"root-variables": _eslint_css.CSSRuleDefinition;
|
|
152
156
|
"apply-usage": _eslint_css.CSSRuleDefinition;
|
|
@@ -246,13 +250,16 @@ declare const allPasikaRuleIds: string[];
|
|
|
246
250
|
* source block: source linting is the Next.js app's job.
|
|
247
251
|
* Use this for a plain TypeScript repository that does not adopt the framework.
|
|
248
252
|
*/
|
|
249
|
-
declare const
|
|
253
|
+
declare const pasikaApp: Linter.Config[];
|
|
250
254
|
/**
|
|
251
255
|
* Next.js app preset: the full adopted-to-the-framework stack. Anything in
|
|
252
|
-
* `
|
|
256
|
+
* `pasikaApp` plus the framework-only blocks — the Next.js-stack manifest
|
|
253
257
|
* requirement, the `src/**` app source rules, and the Tailwind stylesheet
|
|
254
|
-
* rules. `
|
|
258
|
+
* rules. `pasikaApp` is a strict subset of `pasikaNextjsApp`. Consuming this
|
|
259
|
+
* preset (spreading or iterating it) runs the TypeScript alignment diagnostic
|
|
260
|
+
* first, so a mismatched compiler major fails config load with an actionable
|
|
261
|
+
* error instead of crashing every type-aware rule later.
|
|
255
262
|
*/
|
|
256
|
-
declare const
|
|
263
|
+
declare const pasikaNextjsApp: Linter.Config[];
|
|
257
264
|
|
|
258
|
-
export { allPasikaRuleIds, documentationRules, huskyRules,
|
|
265
|
+
export { allPasikaRuleIds, documentationRules, huskyRules, nextjsPackageJsonRules, pasikaApp, pasikaNextjsApp, pasikaPlugin, repoPackageJsonRules, tailwindRules, vulykRules };
|
|
@@ -4,6 +4,53 @@ import jsonPlugin from "@eslint/json";
|
|
|
4
4
|
import markdown from "@eslint/markdown";
|
|
5
5
|
import tsParser from "@typescript-eslint/parser";
|
|
6
6
|
|
|
7
|
+
// eslint/ts-alignment.ts
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { createRequire } from "module";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
var ownRequire = createRequire(import.meta.url);
|
|
12
|
+
var majorOf = (version) => Number(version.split(".")[0]);
|
|
13
|
+
function versionOf(packagePath) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
16
|
+
if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) return null;
|
|
17
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function resolveTypescriptAlignment() {
|
|
23
|
+
let bundled = null;
|
|
24
|
+
try {
|
|
25
|
+
bundled = versionOf(ownRequire.resolve("typescript/package.json"));
|
|
26
|
+
} catch {
|
|
27
|
+
bundled = null;
|
|
28
|
+
}
|
|
29
|
+
let resolved = null;
|
|
30
|
+
try {
|
|
31
|
+
const consumerRequire = createRequire(join(process.cwd(), "package.json"));
|
|
32
|
+
resolved = versionOf(consumerRequire.resolve("typescript/package.json"));
|
|
33
|
+
} catch {
|
|
34
|
+
resolved = null;
|
|
35
|
+
}
|
|
36
|
+
return { bundled, resolved };
|
|
37
|
+
}
|
|
38
|
+
function alignmentError(bundled, resolved) {
|
|
39
|
+
if (bundled === null || resolved === null) return null;
|
|
40
|
+
const bundledMajor = majorOf(bundled);
|
|
41
|
+
const resolvedMajor = majorOf(resolved);
|
|
42
|
+
if (bundledMajor === resolvedMajor) return null;
|
|
43
|
+
const direction = resolvedMajor < bundledMajor ? `Upgrade your typescript to ^${String(bundledMajor)} (pinned exact) so a single hoisted copy serves the whole pipeline.` : `Your repository is ahead of the compiler pasika ships. Pin typescript to ^${String(bundledMajor)} for now and upgrade pasika when it bundles the newer major.`;
|
|
44
|
+
return new Error(
|
|
45
|
+
`[pasika] TypeScript major mismatch: the pasikaNextjsApp preset parses src/** with its bundled @typescript-eslint/parser, which runs on typescript ${bundled}; your repository resolves typescript ${resolved} for the type-aware rules. Two TypeScript majors mean the types the parser builds use a different TypeFlags layout than the rules expect, so every type-aware rule crashes (e.g. "undefined is not iterable" in ts-api-utils). Align them on ${String(bundledMajor)}, then lint will run again. ${direction}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function assertTypescriptAlignment() {
|
|
49
|
+
const { bundled, resolved } = resolveTypescriptAlignment();
|
|
50
|
+
const error = alignmentError(bundled, resolved);
|
|
51
|
+
if (error) throw error;
|
|
52
|
+
}
|
|
53
|
+
|
|
7
54
|
// eslint/rules/filename-case.ts
|
|
8
55
|
import path2 from "path";
|
|
9
56
|
|
|
@@ -473,7 +520,7 @@ var noArbitraryTailwindRule = {
|
|
|
473
520
|
};
|
|
474
521
|
|
|
475
522
|
// eslint/rules/unknown-utility.ts
|
|
476
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
523
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
477
524
|
import path5 from "path";
|
|
478
525
|
var PREFIX_NAMESPACES = {
|
|
479
526
|
bg: ["color"],
|
|
@@ -752,7 +799,7 @@ function readInventory(files) {
|
|
|
752
799
|
for (const file of files) {
|
|
753
800
|
let css2;
|
|
754
801
|
try {
|
|
755
|
-
css2 =
|
|
802
|
+
css2 = readFileSync2(file, "utf8");
|
|
756
803
|
} catch {
|
|
757
804
|
continue;
|
|
758
805
|
}
|
|
@@ -922,7 +969,7 @@ import path7 from "path";
|
|
|
922
969
|
|
|
923
970
|
// eslint/project/parse-module.ts
|
|
924
971
|
import path6 from "path";
|
|
925
|
-
import { readFileSync as
|
|
972
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
926
973
|
import ts2 from "typescript";
|
|
927
974
|
var isPascalCase3 = (name) => /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
928
975
|
var isHookName = (name) => /^use[A-Z]/.test(name);
|
|
@@ -959,7 +1006,7 @@ function classifyValue(name, initializer, isTsx) {
|
|
|
959
1006
|
return "constant";
|
|
960
1007
|
}
|
|
961
1008
|
function parseModule(file) {
|
|
962
|
-
const text =
|
|
1009
|
+
const text = readFileSync3(file, "utf8");
|
|
963
1010
|
const isTsx = file.endsWith(".tsx") || file.endsWith(".jsx");
|
|
964
1011
|
const sourceFile = ts2.createSourceFile(file, text, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
965
1012
|
const imports = [];
|
|
@@ -3207,7 +3254,7 @@ var typeExtractionRule = {
|
|
|
3207
3254
|
|
|
3208
3255
|
// eslint/rules/locale-placement.ts
|
|
3209
3256
|
import path32 from "path";
|
|
3210
|
-
import { readFileSync as
|
|
3257
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3211
3258
|
import ts5 from "typescript";
|
|
3212
3259
|
var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
|
|
3213
3260
|
var camelCase = (name) => name.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase());
|
|
@@ -3264,7 +3311,7 @@ var localePlacementRule = {
|
|
|
3264
3311
|
return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
|
|
3265
3312
|
});
|
|
3266
3313
|
if (!localesFile || file !== localesFile) return {};
|
|
3267
|
-
const placement = localePlacement(
|
|
3314
|
+
const placement = localePlacement(readFileSync4(localesFile, "utf8"));
|
|
3268
3315
|
if (!placement) return {};
|
|
3269
3316
|
const keyReaders = /* @__PURE__ */ new Map();
|
|
3270
3317
|
const keyFeatures = /* @__PURE__ */ new Map();
|
|
@@ -3276,7 +3323,7 @@ var localePlacementRule = {
|
|
|
3276
3323
|
);
|
|
3277
3324
|
if (!importsLocales) continue;
|
|
3278
3325
|
const candidateSegments = segmentsOf(candidateFile, sourceRoot);
|
|
3279
|
-
for (const match of
|
|
3326
|
+
for (const match of readFileSync4(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
|
|
3280
3327
|
const key = match.groups?.key;
|
|
3281
3328
|
if (key === void 0) continue;
|
|
3282
3329
|
const readers = keyReaders.get(key) ?? /* @__PURE__ */ new Set();
|
|
@@ -3613,7 +3660,7 @@ var localeKeyShapeRule = {
|
|
|
3613
3660
|
|
|
3614
3661
|
// eslint/rules/shared-style-dedup.ts
|
|
3615
3662
|
import path35 from "path";
|
|
3616
|
-
import { readFileSync as
|
|
3663
|
+
import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
3617
3664
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
3618
3665
|
var comboCache;
|
|
3619
3666
|
function combosFor(index) {
|
|
@@ -3631,7 +3678,7 @@ function combosFor(index) {
|
|
|
3631
3678
|
}
|
|
3632
3679
|
const combos = /* @__PURE__ */ new Map();
|
|
3633
3680
|
for (const file of files) {
|
|
3634
|
-
const text =
|
|
3681
|
+
const text = readFileSync5(file, "utf8");
|
|
3635
3682
|
for (const match of text.matchAll(CLASS_NAME)) {
|
|
3636
3683
|
const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
|
|
3637
3684
|
if (classes.length < 2) continue;
|
|
@@ -4541,7 +4588,7 @@ var noTemplatePromptRule = {
|
|
|
4541
4588
|
import path41 from "path";
|
|
4542
4589
|
|
|
4543
4590
|
// eslint/rules/documentation/project-index.ts
|
|
4544
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
4591
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
4545
4592
|
import path40 from "path";
|
|
4546
4593
|
var KIND_BY_SUFFIX = [
|
|
4547
4594
|
["-rule.md", "rule"],
|
|
@@ -4559,7 +4606,7 @@ function listMarkdownFiles(dir) {
|
|
|
4559
4606
|
});
|
|
4560
4607
|
}
|
|
4561
4608
|
function extractTitle(filePath) {
|
|
4562
|
-
const content =
|
|
4609
|
+
const content = readFileSync6(filePath, "utf8");
|
|
4563
4610
|
const match = /^# (?<title>.+)$/m.exec(content);
|
|
4564
4611
|
return match?.groups?.title?.trim() ?? "";
|
|
4565
4612
|
}
|
|
@@ -4842,10 +4889,10 @@ var noNestedHowToRule = {
|
|
|
4842
4889
|
};
|
|
4843
4890
|
|
|
4844
4891
|
// eslint/rules/documentation/glossary-term-linking.ts
|
|
4845
|
-
import { readFileSync as
|
|
4892
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4846
4893
|
import path42 from "path";
|
|
4847
4894
|
function extractGlossaryTerms(filePath) {
|
|
4848
|
-
const content =
|
|
4895
|
+
const content = readFileSync7(filePath, "utf8");
|
|
4849
4896
|
const terms = [];
|
|
4850
4897
|
const headingPattern = /^## (?<term>.+)$/gm;
|
|
4851
4898
|
let match;
|
|
@@ -5444,7 +5491,7 @@ import { statSync as statSync6 } from "fs";
|
|
|
5444
5491
|
import path46 from "path";
|
|
5445
5492
|
|
|
5446
5493
|
// eslint/rules/tailwind/source-files.ts
|
|
5447
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
5494
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
5448
5495
|
import path44 from "path";
|
|
5449
5496
|
var CSS_EXTENSIONS = [".css"];
|
|
5450
5497
|
var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
@@ -5475,7 +5522,7 @@ function cachedTextReader() {
|
|
|
5475
5522
|
let text = texts.get(file);
|
|
5476
5523
|
if (text === void 0) {
|
|
5477
5524
|
try {
|
|
5478
|
-
text =
|
|
5525
|
+
text = readFileSync8(file, "utf8");
|
|
5479
5526
|
} catch {
|
|
5480
5527
|
text = "";
|
|
5481
5528
|
}
|
|
@@ -5739,10 +5786,108 @@ var exactVersionRule = {
|
|
|
5739
5786
|
}
|
|
5740
5787
|
};
|
|
5741
5788
|
|
|
5742
|
-
// eslint/rules/package-json/
|
|
5789
|
+
// eslint/rules/package-json/lint-setup.ts
|
|
5743
5790
|
function memberName2(member) {
|
|
5744
5791
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5745
5792
|
}
|
|
5793
|
+
function memberValue2(member) {
|
|
5794
|
+
return member?.value.type === "String" ? member.value.value : void 0;
|
|
5795
|
+
}
|
|
5796
|
+
function stringValues(value) {
|
|
5797
|
+
if (value.type === "String") return [value.value];
|
|
5798
|
+
if (value.type !== "Array") return [];
|
|
5799
|
+
return value.elements.flatMap((element) => element.value.type === "String" ? [element.value.value] : []);
|
|
5800
|
+
}
|
|
5801
|
+
var SOURCE_GLOB_PATTERN = /(?:^|[^a-z])(?:[cm]?[jt]sx?)(?:[^a-z]|$)/i;
|
|
5802
|
+
var DIRECT_ESLINT_PATTERN = /(?:^|&&|\|\||;)\s*(?:npx\s+)?eslint(?:\s|$)/;
|
|
5803
|
+
var REPOSITORY_ARGUMENT_PATTERN = /(?:^|\s)\.(?=\s|$)/;
|
|
5804
|
+
var PRETTIER_PATTERN = /\bprettier\b/;
|
|
5805
|
+
var CHECK_FLAG_PATTERN = /(?:^|\s)(?:--check|-c)(?:\s|$)/;
|
|
5806
|
+
function runsEslintDirectly(command) {
|
|
5807
|
+
return DIRECT_ESLINT_PATTERN.test(command);
|
|
5808
|
+
}
|
|
5809
|
+
function runsPrettier(command) {
|
|
5810
|
+
return PRETTIER_PATTERN.test(command);
|
|
5811
|
+
}
|
|
5812
|
+
function runsNamedScript(command, name) {
|
|
5813
|
+
return command.includes(`npm run ${name}`);
|
|
5814
|
+
}
|
|
5815
|
+
var lintSetupRule = {
|
|
5816
|
+
meta: {
|
|
5817
|
+
schema: [],
|
|
5818
|
+
type: "problem",
|
|
5819
|
+
docs: {
|
|
5820
|
+
description: "Require full-repository lint/format scripts and argument-free staged-file scripts that lint-staged runs by name."
|
|
5821
|
+
}
|
|
5822
|
+
},
|
|
5823
|
+
create(context) {
|
|
5824
|
+
return {
|
|
5825
|
+
Document(node) {
|
|
5826
|
+
const root = node.body;
|
|
5827
|
+
if (root.type !== "Object") return;
|
|
5828
|
+
const scripts = root.members.find((member) => memberName2(member) === "scripts");
|
|
5829
|
+
const scriptMembers = scripts?.value.type === "Object" ? scripts.value.members : [];
|
|
5830
|
+
const findScript = (name) => scriptMembers.find((member) => memberName2(member) === name);
|
|
5831
|
+
const lint = findScript("lint");
|
|
5832
|
+
const lintCommand = memberValue2(lint);
|
|
5833
|
+
if (lintCommand === void 0 || !runsEslintDirectly(lintCommand) || !REPOSITORY_ARGUMENT_PATTERN.test(lintCommand)) {
|
|
5834
|
+
context.report({
|
|
5835
|
+
node: lint ?? node,
|
|
5836
|
+
message: 'package.json must declare a "lint" script that runs ESLint across the repository (e.g. "eslint .").'
|
|
5837
|
+
});
|
|
5838
|
+
}
|
|
5839
|
+
const format = findScript("format");
|
|
5840
|
+
const formatCommand = memberValue2(format);
|
|
5841
|
+
if (formatCommand === void 0 || !runsPrettier(formatCommand) || !CHECK_FLAG_PATTERN.test(formatCommand) || !REPOSITORY_ARGUMENT_PATTERN.test(formatCommand)) {
|
|
5842
|
+
context.report({
|
|
5843
|
+
node: format ?? node,
|
|
5844
|
+
message: 'package.json must declare a "format" script that runs prettier --check across the repository.'
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
const lintStaged = findScript("lint:staged");
|
|
5848
|
+
const lintStagedCommand = memberValue2(lintStaged);
|
|
5849
|
+
if (lintStagedCommand === void 0 || !runsEslintDirectly(lintStagedCommand) || REPOSITORY_ARGUMENT_PATTERN.test(lintStagedCommand)) {
|
|
5850
|
+
context.report({
|
|
5851
|
+
node: lintStaged ?? node,
|
|
5852
|
+
message: 'package.json must declare a "lint:staged" script that runs ESLint with no repository-wide argument (e.g. "eslint --fix").'
|
|
5853
|
+
});
|
|
5854
|
+
}
|
|
5855
|
+
const formatStaged = findScript("format:staged");
|
|
5856
|
+
const formatStagedCommand = memberValue2(formatStaged);
|
|
5857
|
+
if (formatStagedCommand === void 0 || !runsPrettier(formatStagedCommand) || REPOSITORY_ARGUMENT_PATTERN.test(formatStagedCommand)) {
|
|
5858
|
+
context.report({
|
|
5859
|
+
node: formatStaged ?? node,
|
|
5860
|
+
message: 'package.json must declare a "format:staged" script that runs prettier with no repository-wide argument (e.g. "prettier --write").'
|
|
5861
|
+
});
|
|
5862
|
+
}
|
|
5863
|
+
const lintStagedConfig = root.members.find((member) => memberName2(member) === "lint-staged");
|
|
5864
|
+
const runsStagedScript = (name) => lintStagedConfig?.value.type === "Object" && lintStagedConfig.value.members.some(
|
|
5865
|
+
(member) => stringValues(member.value).some((command) => runsNamedScript(command, name))
|
|
5866
|
+
);
|
|
5867
|
+
const hasStagedLintEntry = lintStagedConfig?.value.type === "Object" && lintStagedConfig.value.members.some(
|
|
5868
|
+
(member) => SOURCE_GLOB_PATTERN.test(memberName2(member)) && stringValues(member.value).some((command) => runsNamedScript(command, "lint:staged"))
|
|
5869
|
+
);
|
|
5870
|
+
if (!hasStagedLintEntry) {
|
|
5871
|
+
context.report({
|
|
5872
|
+
node: lintStagedConfig ?? node,
|
|
5873
|
+
message: 'package.json lint-staged must run "npm run lint:staged --" for staged JavaScript or TypeScript files.'
|
|
5874
|
+
});
|
|
5875
|
+
}
|
|
5876
|
+
if (!runsStagedScript("format:staged")) {
|
|
5877
|
+
context.report({
|
|
5878
|
+
node: lintStagedConfig ?? node,
|
|
5879
|
+
message: 'package.json lint-staged must run "npm run format:staged --" for staged files ESLint does not already format.'
|
|
5880
|
+
});
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
};
|
|
5884
|
+
}
|
|
5885
|
+
};
|
|
5886
|
+
|
|
5887
|
+
// eslint/rules/package-json/no-vulyk-dependency.ts
|
|
5888
|
+
function memberName3(member) {
|
|
5889
|
+
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5890
|
+
}
|
|
5746
5891
|
var noVulykDependencyRule = {
|
|
5747
5892
|
meta: {
|
|
5748
5893
|
schema: [],
|
|
@@ -5757,9 +5902,9 @@ var noVulykDependencyRule = {
|
|
|
5757
5902
|
const root = node.body;
|
|
5758
5903
|
if (root.type !== "Object") return;
|
|
5759
5904
|
for (const key of ["dependencies", "devDependencies"]) {
|
|
5760
|
-
const section = root.members.find((member) =>
|
|
5905
|
+
const section = root.members.find((member) => memberName3(member) === key);
|
|
5761
5906
|
if (section?.value.type !== "Object") continue;
|
|
5762
|
-
const vulyk = section.value.members.find((member) =>
|
|
5907
|
+
const vulyk = section.value.members.find((member) => memberName3(member) === "vulyk");
|
|
5763
5908
|
if (vulyk) {
|
|
5764
5909
|
context.report({
|
|
5765
5910
|
node: vulyk,
|
|
@@ -5789,15 +5934,19 @@ var NEXTJS_STACK_DEV_DEPENDENCIES = [
|
|
|
5789
5934
|
"prettier",
|
|
5790
5935
|
"husky",
|
|
5791
5936
|
"lint-staged",
|
|
5792
|
-
"zirka"
|
|
5937
|
+
"zirka",
|
|
5938
|
+
"jsdom",
|
|
5939
|
+
"@vitejs/plugin-react",
|
|
5940
|
+
"@testing-library/react",
|
|
5941
|
+
"@testing-library/dom"
|
|
5793
5942
|
];
|
|
5794
|
-
function
|
|
5943
|
+
function memberName4(member) {
|
|
5795
5944
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5796
5945
|
}
|
|
5797
5946
|
function sectionPackages(root, section) {
|
|
5798
|
-
const member = root.type === "Object" ? root.members.find((entry) =>
|
|
5947
|
+
const member = root.type === "Object" ? root.members.find((entry) => memberName4(entry) === section) : void 0;
|
|
5799
5948
|
if (member?.value.type !== "Object") return /* @__PURE__ */ new Set();
|
|
5800
|
-
return new Set(member.value.members.map(
|
|
5949
|
+
return new Set(member.value.members.map(memberName4));
|
|
5801
5950
|
}
|
|
5802
5951
|
var nextjsStackRule = {
|
|
5803
5952
|
meta: {
|
|
@@ -5833,19 +5982,151 @@ var nextjsStackRule = {
|
|
|
5833
5982
|
}
|
|
5834
5983
|
};
|
|
5835
5984
|
|
|
5985
|
+
// eslint/rules/package-json/vitest-coverage.ts
|
|
5986
|
+
import { existsSync as existsSync2, readFileSync as readFileSync9 } from "fs";
|
|
5987
|
+
import path47 from "path";
|
|
5988
|
+
function memberName5(member) {
|
|
5989
|
+
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5990
|
+
}
|
|
5991
|
+
function memberValue3(member) {
|
|
5992
|
+
return member?.value.type === "String" ? member.value.value : void 0;
|
|
5993
|
+
}
|
|
5994
|
+
function stringValues2(value) {
|
|
5995
|
+
if (value.type === "String") return [value.value];
|
|
5996
|
+
if (value.type !== "Array") return [];
|
|
5997
|
+
return value.elements.flatMap((element) => element.value.type === "String" ? [element.value.value] : []);
|
|
5998
|
+
}
|
|
5999
|
+
var VITEST_CONFIG_NAMES = [
|
|
6000
|
+
"vitest.config.ts",
|
|
6001
|
+
"vitest.config.mts",
|
|
6002
|
+
"vitest.config.cts",
|
|
6003
|
+
"vitest.config.js",
|
|
6004
|
+
"vitest.config.mjs",
|
|
6005
|
+
"vitest.config.cjs"
|
|
6006
|
+
];
|
|
6007
|
+
var THRESHOLD_METRICS = ["lines", "functions", "branches", "statements"];
|
|
6008
|
+
var SOURCE_GLOB_PATTERN2 = /(?:^|[^a-z])(?:[cm]?[jt]sx?)(?:[^a-z]|$)/i;
|
|
6009
|
+
var COVERAGE_FLAG_PATTERN = /(?:^|\s)--coverage(?:[=\s]|$)/;
|
|
6010
|
+
var RELATED_PATTERN = /\brelated\b/;
|
|
6011
|
+
var AUTO_UPDATE_PATTERN = /autoUpdate\s*:\s*true/;
|
|
6012
|
+
var CHANGED_FLAG_PATTERN = /--coverage\.changed\b/;
|
|
6013
|
+
var PER_FILE_FLAG_PATTERN = /--coverage\.thresholds\.perFile\b/;
|
|
6014
|
+
var AUTO_UPDATE_DISABLED_FLAG_PATTERN = /--coverage\.thresholds\.autoUpdate=false\b/;
|
|
6015
|
+
function hasEightyOrAboveFlag(command, metric) {
|
|
6016
|
+
return new RegExp(`--coverage\\.thresholds\\.${metric}=(?:8\\d|9\\d|100)\\b`).test(command);
|
|
6017
|
+
}
|
|
6018
|
+
var vitestCoverageRule = {
|
|
6019
|
+
meta: {
|
|
6020
|
+
schema: [],
|
|
6021
|
+
type: "problem",
|
|
6022
|
+
docs: {
|
|
6023
|
+
description: "Require Vitest unit-test scripts, the V8 provider, a rising coverage threshold, and an 80% floor on staged files via lint-staged."
|
|
6024
|
+
}
|
|
6025
|
+
},
|
|
6026
|
+
create(context) {
|
|
6027
|
+
return {
|
|
6028
|
+
Document(node) {
|
|
6029
|
+
const root = node.body;
|
|
6030
|
+
if (root.type !== "Object") return;
|
|
6031
|
+
const devDependencies = root.members.find((member) => memberName5(member) === "devDependencies");
|
|
6032
|
+
const devDependencyNames = new Set(
|
|
6033
|
+
devDependencies?.value.type === "Object" ? devDependencies.value.members.map(memberName5) : []
|
|
6034
|
+
);
|
|
6035
|
+
if (!devDependencyNames.has("vitest")) {
|
|
6036
|
+
context.report({ node, message: "vitest must be listed in package.json as a devDependency." });
|
|
6037
|
+
}
|
|
6038
|
+
if (!devDependencyNames.has("@vitest/coverage-v8")) {
|
|
6039
|
+
context.report({
|
|
6040
|
+
node,
|
|
6041
|
+
message: "@vitest/coverage-v8 must be listed in package.json as a devDependency."
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
const scripts = root.members.find((member) => memberName5(member) === "scripts");
|
|
6045
|
+
const scriptMembers = scripts?.value.type === "Object" ? scripts.value.members : [];
|
|
6046
|
+
const unit = scriptMembers.find((member) => memberName5(member) === "test:unit");
|
|
6047
|
+
const unitCommand = memberValue3(unit);
|
|
6048
|
+
if (unitCommand === void 0 || !/\bvitest\b/.test(unitCommand) || COVERAGE_FLAG_PATTERN.test(unitCommand)) {
|
|
6049
|
+
context.report({
|
|
6050
|
+
node: unit ?? node,
|
|
6051
|
+
message: 'package.json must declare a "test:unit" script that runs Vitest without coverage.'
|
|
6052
|
+
});
|
|
6053
|
+
}
|
|
6054
|
+
const coverage = scriptMembers.find((member) => memberName5(member) === "test:unit:coverage");
|
|
6055
|
+
const coverageCommand = memberValue3(coverage);
|
|
6056
|
+
if (coverageCommand === void 0 || !/\bvitest\b/.test(coverageCommand) || !COVERAGE_FLAG_PATTERN.test(coverageCommand)) {
|
|
6057
|
+
context.report({
|
|
6058
|
+
node: coverage ?? node,
|
|
6059
|
+
message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
6062
|
+
const configName = VITEST_CONFIG_NAMES.find((name) => existsSync2(path47.join(context.cwd, name)));
|
|
6063
|
+
if (!configName) {
|
|
6064
|
+
context.report({
|
|
6065
|
+
node,
|
|
6066
|
+
message: "No vitest config found. Create one with a coverage threshold above zero for lines, functions, branches, and statements."
|
|
6067
|
+
});
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
const content = readFileSync9(path47.join(context.cwd, configName), "utf8");
|
|
6071
|
+
for (const metric of THRESHOLD_METRICS) {
|
|
6072
|
+
if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
|
|
6073
|
+
context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
if (!AUTO_UPDATE_PATTERN.test(content)) {
|
|
6077
|
+
context.report({
|
|
6078
|
+
node,
|
|
6079
|
+
message: `${configName} must set coverage.thresholds.autoUpdate to true.`
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
const changedCoverage = scriptMembers.find((member) => memberName5(member) === "test:unit:coverage:staged");
|
|
6083
|
+
const changedCoverageCommand = memberValue3(changedCoverage);
|
|
6084
|
+
if (changedCoverageCommand === void 0 || !/\bvitest\b/.test(changedCoverageCommand) || !RELATED_PATTERN.test(changedCoverageCommand) || !COVERAGE_FLAG_PATTERN.test(changedCoverageCommand)) {
|
|
6085
|
+
context.report({
|
|
6086
|
+
node: changedCoverage ?? node,
|
|
6087
|
+
message: 'package.json must declare a "test:unit:coverage:staged" script that runs vitest related with coverage.'
|
|
6088
|
+
});
|
|
6089
|
+
}
|
|
6090
|
+
const lintStaged = root.members.find((member) => memberName5(member) === "lint-staged");
|
|
6091
|
+
const hasStagedChangedCoverage = lintStaged?.value.type === "Object" && lintStaged.value.members.some(
|
|
6092
|
+
(member) => SOURCE_GLOB_PATTERN2.test(memberName5(member)) && stringValues2(member.value).some((command) => command.includes("npm run test:unit:coverage:staged"))
|
|
6093
|
+
);
|
|
6094
|
+
if (!hasStagedChangedCoverage) {
|
|
6095
|
+
context.report({
|
|
6096
|
+
node: lintStaged ?? node,
|
|
6097
|
+
message: 'package.json lint-staged must run "npm run test:unit:coverage:staged" for staged JavaScript or TypeScript files.'
|
|
6098
|
+
});
|
|
6099
|
+
}
|
|
6100
|
+
const changedCoverageCommandText = changedCoverageCommand ?? "";
|
|
6101
|
+
const hasEightyFloor = THRESHOLD_METRICS.every(
|
|
6102
|
+
(metric) => hasEightyOrAboveFlag(changedCoverageCommandText, metric)
|
|
6103
|
+
);
|
|
6104
|
+
if (!CHANGED_FLAG_PATTERN.test(changedCoverageCommandText) || !PER_FILE_FLAG_PATTERN.test(changedCoverageCommandText) || !hasEightyFloor || !AUTO_UPDATE_DISABLED_FLAG_PATTERN.test(changedCoverageCommandText)) {
|
|
6105
|
+
context.report({
|
|
6106
|
+
node: changedCoverage ?? node,
|
|
6107
|
+
message: 'package.json "test:unit:coverage:staged" script must pass --coverage.changed, a --coverage.thresholds.perFile of at least 80 for lines, functions, branches, and statements, and --coverage.thresholds.autoUpdate=false.'
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
};
|
|
6112
|
+
}
|
|
6113
|
+
};
|
|
6114
|
+
|
|
5836
6115
|
// eslint/rules/package-json/index.ts
|
|
5837
6116
|
var repoPackageJsonRules = {
|
|
5838
6117
|
"no-vulyk-dependency": noVulykDependencyRule,
|
|
5839
|
-
"exact-version": exactVersionRule
|
|
6118
|
+
"exact-version": exactVersionRule,
|
|
6119
|
+
"lint-setup": lintSetupRule,
|
|
6120
|
+
"vitest-coverage": vitestCoverageRule
|
|
5840
6121
|
};
|
|
5841
6122
|
var nextjsPackageJsonRules = {
|
|
5842
6123
|
"nextjs-stack": nextjsStackRule
|
|
5843
6124
|
};
|
|
5844
6125
|
|
|
5845
6126
|
// eslint/rules/husky/husky-hook.ts
|
|
5846
|
-
import { existsSync as
|
|
5847
|
-
import
|
|
5848
|
-
function
|
|
6127
|
+
import { existsSync as existsSync3, readFileSync as readFileSync10 } from "fs";
|
|
6128
|
+
import path48 from "path";
|
|
6129
|
+
function memberName6(member) {
|
|
5849
6130
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5850
6131
|
}
|
|
5851
6132
|
var huskyHookRule = {
|
|
@@ -5853,7 +6134,7 @@ var huskyHookRule = {
|
|
|
5853
6134
|
schema: [],
|
|
5854
6135
|
type: "problem",
|
|
5855
6136
|
docs: {
|
|
5856
|
-
description: "Require a pre-commit hook that runs lint-staged
|
|
6137
|
+
description: "Require a pre-commit hook that runs lint-staged and the libyear drift check directly, and the typecheck and suppression-file ratchet through named package.json scripts."
|
|
5857
6138
|
}
|
|
5858
6139
|
},
|
|
5859
6140
|
create(context) {
|
|
@@ -5861,29 +6142,39 @@ var huskyHookRule = {
|
|
|
5861
6142
|
Document(node) {
|
|
5862
6143
|
const root = node.body;
|
|
5863
6144
|
if (root.type !== "Object") return;
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
if (!existsSync2(hookPath)) {
|
|
6145
|
+
if (!context.filename.endsWith("package.json")) return;
|
|
6146
|
+
const hookPath = path48.join(context.cwd, ".husky", "pre-commit");
|
|
6147
|
+
if (!existsSync3(hookPath)) {
|
|
5868
6148
|
context.report({
|
|
5869
6149
|
node,
|
|
5870
6150
|
message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
|
|
5871
6151
|
});
|
|
5872
6152
|
return;
|
|
5873
6153
|
}
|
|
5874
|
-
const content =
|
|
6154
|
+
const content = readFileSync10(hookPath, "utf8");
|
|
6155
|
+
const scripts = root.members.find((member) => memberName6(member) === "scripts");
|
|
6156
|
+
const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName6) : []);
|
|
6157
|
+
const requireNamedScript = (name) => {
|
|
6158
|
+
if (!scriptNames.has(name)) {
|
|
6159
|
+
context.report({ node, message: `package.json must declare a "${name}" script.` });
|
|
6160
|
+
}
|
|
6161
|
+
if (!content.includes(`npm run ${name}`)) {
|
|
6162
|
+
context.report({ node, message: `.husky/pre-commit must run npm run ${name}.` });
|
|
6163
|
+
}
|
|
6164
|
+
};
|
|
5875
6165
|
if (!content.includes("lint-staged")) {
|
|
5876
6166
|
context.report({ node, message: ".husky/pre-commit must run lint-staged." });
|
|
5877
6167
|
}
|
|
5878
|
-
|
|
5879
|
-
context.report({ node, message: ".husky/pre-commit must run npm run typecheck." });
|
|
5880
|
-
}
|
|
6168
|
+
requireNamedScript("typecheck");
|
|
5881
6169
|
if (!content.includes("libyear --limit-major-individual=1")) {
|
|
5882
6170
|
context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
|
|
5883
6171
|
}
|
|
5884
|
-
const
|
|
6172
|
+
const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
|
|
6173
|
+
if (existsSync3(suppressionsPath)) {
|
|
6174
|
+
requireNamedScript("lint:prune");
|
|
6175
|
+
}
|
|
5885
6176
|
if (scripts?.value.type === "Object") {
|
|
5886
|
-
const prepare = scripts.value.members.find((member) =>
|
|
6177
|
+
const prepare = scripts.value.members.find((member) => memberName6(member) === "prepare");
|
|
5887
6178
|
if (prepare?.value.type === "String" && !prepare.value.value.includes("husky")) {
|
|
5888
6179
|
context.report({
|
|
5889
6180
|
node: prepare,
|
|
@@ -5902,8 +6193,8 @@ var huskyRules = {
|
|
|
5902
6193
|
};
|
|
5903
6194
|
|
|
5904
6195
|
// eslint/rules/vulyk/vulyk-docs.ts
|
|
5905
|
-
import { existsSync as
|
|
5906
|
-
import
|
|
6196
|
+
import { existsSync as existsSync4, readFileSync as readFileSync11 } from "fs";
|
|
6197
|
+
import path49 from "path";
|
|
5907
6198
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
5908
6199
|
var BASE_REQUIRED_DOCS = [
|
|
5909
6200
|
{ name: "documentation-guide", path: "docs/documentation-guide" },
|
|
@@ -5914,13 +6205,13 @@ var NEXTJS_REQUIRED_DOCS = [
|
|
|
5914
6205
|
{ name: "next-codebase-guide", path: "docs/next-codebase-guide" },
|
|
5915
6206
|
{ name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
|
|
5916
6207
|
];
|
|
5917
|
-
function
|
|
6208
|
+
function memberName7(member) {
|
|
5918
6209
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5919
6210
|
}
|
|
5920
6211
|
function hasDependency(root, name) {
|
|
5921
|
-
const section = root.members.find((member) =>
|
|
6212
|
+
const section = root.members.find((member) => memberName7(member) === "dependencies");
|
|
5922
6213
|
if (section?.value.type !== "Object") return false;
|
|
5923
|
-
return section.value.members.some((member) =>
|
|
6214
|
+
return section.value.members.some((member) => memberName7(member) === name);
|
|
5924
6215
|
}
|
|
5925
6216
|
var vulykDocsRule = {
|
|
5926
6217
|
meta: {
|
|
@@ -5936,16 +6227,16 @@ var vulykDocsRule = {
|
|
|
5936
6227
|
if (!context.filename.endsWith("package.json")) return;
|
|
5937
6228
|
const root = node.body;
|
|
5938
6229
|
if (root.type !== "Object") return;
|
|
5939
|
-
const projectRoot =
|
|
5940
|
-
const configPath =
|
|
5941
|
-
if (!
|
|
6230
|
+
const projectRoot = path49.dirname(path49.resolve(context.filename));
|
|
6231
|
+
const configPath = path49.join(projectRoot, "vulyk.config.ts");
|
|
6232
|
+
if (!existsSync4(configPath)) {
|
|
5942
6233
|
context.report({
|
|
5943
6234
|
node,
|
|
5944
6235
|
message: "No vulyk.config.ts found. Run npx vulyk@latest init to create one that tracks the framework's docs."
|
|
5945
6236
|
});
|
|
5946
6237
|
return;
|
|
5947
6238
|
}
|
|
5948
|
-
const config =
|
|
6239
|
+
const config = readFileSync11(configPath, "utf8");
|
|
5949
6240
|
if (!config.includes(PASIKA_REPO)) {
|
|
5950
6241
|
context.report({
|
|
5951
6242
|
node,
|
|
@@ -5962,8 +6253,8 @@ var vulykDocsRule = {
|
|
|
5962
6253
|
});
|
|
5963
6254
|
}
|
|
5964
6255
|
}
|
|
5965
|
-
const agentsPath =
|
|
5966
|
-
if (!
|
|
6256
|
+
const agentsPath = path49.join(projectRoot, "AGENTS.md");
|
|
6257
|
+
if (!existsSync4(agentsPath)) {
|
|
5967
6258
|
context.report({
|
|
5968
6259
|
node,
|
|
5969
6260
|
message: "No AGENTS.md found. Run npx vulyk@latest agents to generate the agent file that routes to the tracked docs."
|
|
@@ -5980,7 +6271,7 @@ var vulykRules = {
|
|
|
5980
6271
|
};
|
|
5981
6272
|
|
|
5982
6273
|
// eslint/index.ts
|
|
5983
|
-
var
|
|
6274
|
+
var pasikaNextjsAppRules = {
|
|
5984
6275
|
// Framework-agnostic TypeScript rules.
|
|
5985
6276
|
"filename-case": filenameCaseRule,
|
|
5986
6277
|
"import-boundaries": importBoundariesRule,
|
|
@@ -6030,7 +6321,7 @@ var pasikaPlugin = {
|
|
|
6030
6321
|
rules: {
|
|
6031
6322
|
// The shared plugin must register every rule the preset blocks reference,
|
|
6032
6323
|
// so all rule sets live here.
|
|
6033
|
-
...
|
|
6324
|
+
...pasikaNextjsAppRules,
|
|
6034
6325
|
...documentationRules,
|
|
6035
6326
|
...tailwindRules,
|
|
6036
6327
|
...repoPackageJsonRules,
|
|
@@ -6043,7 +6334,7 @@ var jsonLanguage = { languages: { json: jsonPlugin.languages.json } };
|
|
|
6043
6334
|
function ruleIds(rules2) {
|
|
6044
6335
|
return Object.keys(rules2).map((name) => `pasika/${name}`);
|
|
6045
6336
|
}
|
|
6046
|
-
var
|
|
6337
|
+
var pasikaNextjsAppRuleIds = ruleIds(pasikaNextjsAppRules);
|
|
6047
6338
|
var documentationRuleIds = ruleIds(documentationRules);
|
|
6048
6339
|
var tailwindRuleIds = ruleIds(tailwindRules);
|
|
6049
6340
|
var repoPackageJsonRuleIds = ruleIds(repoPackageJsonRules);
|
|
@@ -6051,7 +6342,7 @@ var nextjsPackageJsonRuleIds = ruleIds(nextjsPackageJsonRules);
|
|
|
6051
6342
|
var huskyRuleIds = ruleIds(huskyRules);
|
|
6052
6343
|
var vulykRuleIds = ruleIds(vulykRules);
|
|
6053
6344
|
var allPasikaRuleIds = [
|
|
6054
|
-
...
|
|
6345
|
+
...pasikaNextjsAppRuleIds,
|
|
6055
6346
|
...documentationRuleIds,
|
|
6056
6347
|
...tailwindRuleIds,
|
|
6057
6348
|
...repoPackageJsonRuleIds,
|
|
@@ -6059,7 +6350,7 @@ var allPasikaRuleIds = [
|
|
|
6059
6350
|
...huskyRuleIds,
|
|
6060
6351
|
...vulykRuleIds
|
|
6061
6352
|
];
|
|
6062
|
-
var
|
|
6353
|
+
var pasikaAppLanguageOptions = {
|
|
6063
6354
|
parser: tsParser,
|
|
6064
6355
|
parserOptions: {
|
|
6065
6356
|
ecmaVersion: "latest",
|
|
@@ -6067,13 +6358,13 @@ var typescriptAppLanguageOptions = {
|
|
|
6067
6358
|
ecmaFeatures: { jsx: true }
|
|
6068
6359
|
}
|
|
6069
6360
|
};
|
|
6070
|
-
var
|
|
6361
|
+
var pasikaNextjsAppConfig = {
|
|
6071
6362
|
files: ["src/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}"],
|
|
6072
|
-
languageOptions:
|
|
6363
|
+
languageOptions: pasikaAppLanguageOptions,
|
|
6073
6364
|
plugins: {
|
|
6074
6365
|
pasika: pasikaPlugin
|
|
6075
6366
|
},
|
|
6076
|
-
rules: Object.fromEntries(
|
|
6367
|
+
rules: Object.fromEntries(pasikaNextjsAppRuleIds.map((id) => [id, "error"]))
|
|
6077
6368
|
};
|
|
6078
6369
|
var tailwindStructureRules = {
|
|
6079
6370
|
files: ["src/**/globals.css"],
|
|
@@ -6097,7 +6388,7 @@ var tailwindImportGraph = {
|
|
|
6097
6388
|
languageOptions: { tolerant: true },
|
|
6098
6389
|
rules: { "pasika/css-entry-point": "error" }
|
|
6099
6390
|
};
|
|
6100
|
-
var
|
|
6391
|
+
var pasikaAppPackageJsonConfig = {
|
|
6101
6392
|
files: ["package.json"],
|
|
6102
6393
|
plugins: {
|
|
6103
6394
|
json: jsonLanguage,
|
|
@@ -6106,7 +6397,7 @@ var typescriptAppPackageJsonConfig = {
|
|
|
6106
6397
|
language: "json/json",
|
|
6107
6398
|
rules: Object.fromEntries([...repoPackageJsonRuleIds, ...huskyRuleIds, ...vulykRuleIds].map((id) => [id, "error"]))
|
|
6108
6399
|
};
|
|
6109
|
-
var
|
|
6400
|
+
var pasikaNextjsAppPackageJsonConfig = {
|
|
6110
6401
|
files: ["package.json"],
|
|
6111
6402
|
plugins: {
|
|
6112
6403
|
json: jsonLanguage,
|
|
@@ -6133,27 +6424,39 @@ var documentationConfig = {
|
|
|
6133
6424
|
language: "markdown/gfm",
|
|
6134
6425
|
rules: Object.fromEntries(documentationRuleIds.map((id) => [id, "error"]))
|
|
6135
6426
|
};
|
|
6136
|
-
var
|
|
6137
|
-
|
|
6427
|
+
var pasikaApp = [
|
|
6428
|
+
pasikaAppPackageJsonConfig,
|
|
6138
6429
|
zirkaConfig,
|
|
6139
6430
|
documentationConfig
|
|
6140
6431
|
];
|
|
6141
|
-
var
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6432
|
+
var pasikaNextjsAppWithDiagnostic = (preset) => {
|
|
6433
|
+
let checked = false;
|
|
6434
|
+
return new Proxy(preset, {
|
|
6435
|
+
get(target, property, receiver) {
|
|
6436
|
+
if (!checked && (property === Symbol.iterator || property === "length")) {
|
|
6437
|
+
checked = true;
|
|
6438
|
+
assertTypescriptAlignment();
|
|
6439
|
+
}
|
|
6440
|
+
return Reflect.get(target, property, receiver);
|
|
6441
|
+
}
|
|
6442
|
+
});
|
|
6443
|
+
};
|
|
6444
|
+
var pasikaNextjsApp = pasikaNextjsAppWithDiagnostic([
|
|
6445
|
+
...pasikaApp,
|
|
6446
|
+
pasikaNextjsAppPackageJsonConfig,
|
|
6447
|
+
pasikaNextjsAppConfig,
|
|
6145
6448
|
tailwindStructureRules,
|
|
6146
6449
|
tailwindImportGraph
|
|
6147
|
-
];
|
|
6450
|
+
]);
|
|
6148
6451
|
export {
|
|
6149
6452
|
allPasikaRuleIds,
|
|
6150
6453
|
documentationRules,
|
|
6151
6454
|
huskyRules,
|
|
6152
|
-
nextjsApp,
|
|
6153
6455
|
nextjsPackageJsonRules,
|
|
6456
|
+
pasikaApp,
|
|
6457
|
+
pasikaNextjsApp,
|
|
6154
6458
|
pasikaPlugin,
|
|
6155
6459
|
repoPackageJsonRules,
|
|
6156
6460
|
tailwindRules,
|
|
6157
|
-
typescriptApp,
|
|
6158
6461
|
vulykRules
|
|
6159
6462
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pasika",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Reusable agent setup package",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,15 +22,23 @@
|
|
|
22
22
|
"coverage": "tsx scripts/coverage.ts",
|
|
23
23
|
"dogfood": "tsx scripts/dogfood.ts",
|
|
24
24
|
"fix": "eslint . --fix",
|
|
25
|
+
"format": "prettier --check . --ignore-unknown",
|
|
26
|
+
"format:staged": "prettier --write --ignore-unknown",
|
|
25
27
|
"lint": "eslint .",
|
|
28
|
+
"lint:staged": "eslint --fix",
|
|
26
29
|
"prepack": "npm run build",
|
|
27
30
|
"prepare": "husky",
|
|
28
|
-
"test": "
|
|
31
|
+
"test:unit": "vitest run",
|
|
32
|
+
"test:unit:coverage": "vitest run --coverage",
|
|
33
|
+
"test:unit:coverage:staged": "vitest related --run --coverage --coverage.changed --coverage.thresholds.perFile --coverage.thresholds.lines=80 --coverage.thresholds.functions=80 --coverage.thresholds.branches=80 --coverage.thresholds.statements=80 --coverage.thresholds.autoUpdate=false",
|
|
29
34
|
"typecheck": "tsc --noEmit"
|
|
30
35
|
},
|
|
31
36
|
"lint-staged": {
|
|
32
|
-
"!(*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx)": "
|
|
33
|
-
"*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx":
|
|
37
|
+
"!(*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx)": "npm run format:staged --",
|
|
38
|
+
"*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx": [
|
|
39
|
+
"npm run lint:staged --",
|
|
40
|
+
"npm run test:unit:coverage:staged --"
|
|
41
|
+
]
|
|
34
42
|
},
|
|
35
43
|
"dependencies": {
|
|
36
44
|
"@typescript-eslint/parser": "8.68.0",
|
|
@@ -45,6 +53,7 @@
|
|
|
45
53
|
"@types/estree": "1.0.9",
|
|
46
54
|
"@types/mdast": "4.0.4",
|
|
47
55
|
"@types/node": "26.4.0",
|
|
56
|
+
"@vitest/coverage-v8": "5.0.0",
|
|
48
57
|
"eslint": "10.9.1",
|
|
49
58
|
"husky": "9.1.7",
|
|
50
59
|
"lint-staged": "17.4.1",
|
|
@@ -52,6 +61,7 @@
|
|
|
52
61
|
"tsup": "8.5.1",
|
|
53
62
|
"tsx": "4.23.12",
|
|
54
63
|
"typescript": "6.0.3",
|
|
64
|
+
"vitest": "5.0.0",
|
|
55
65
|
"zirka": "0.0.48",
|
|
56
66
|
"zod": "4.4.3"
|
|
57
67
|
},
|