pasika 0.5.18 → 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 +1 -1
- package/dist/eslint/pasika/index.d.ts +4 -0
- package/dist/eslint/pasika/index.js +277 -33
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -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;
|
|
@@ -5786,10 +5786,108 @@ var exactVersionRule = {
|
|
|
5786
5786
|
}
|
|
5787
5787
|
};
|
|
5788
5788
|
|
|
5789
|
-
// eslint/rules/package-json/
|
|
5789
|
+
// eslint/rules/package-json/lint-setup.ts
|
|
5790
5790
|
function memberName2(member) {
|
|
5791
5791
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5792
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
|
+
}
|
|
5793
5891
|
var noVulykDependencyRule = {
|
|
5794
5892
|
meta: {
|
|
5795
5893
|
schema: [],
|
|
@@ -5804,9 +5902,9 @@ var noVulykDependencyRule = {
|
|
|
5804
5902
|
const root = node.body;
|
|
5805
5903
|
if (root.type !== "Object") return;
|
|
5806
5904
|
for (const key of ["dependencies", "devDependencies"]) {
|
|
5807
|
-
const section = root.members.find((member) =>
|
|
5905
|
+
const section = root.members.find((member) => memberName3(member) === key);
|
|
5808
5906
|
if (section?.value.type !== "Object") continue;
|
|
5809
|
-
const vulyk = section.value.members.find((member) =>
|
|
5907
|
+
const vulyk = section.value.members.find((member) => memberName3(member) === "vulyk");
|
|
5810
5908
|
if (vulyk) {
|
|
5811
5909
|
context.report({
|
|
5812
5910
|
node: vulyk,
|
|
@@ -5836,15 +5934,19 @@ var NEXTJS_STACK_DEV_DEPENDENCIES = [
|
|
|
5836
5934
|
"prettier",
|
|
5837
5935
|
"husky",
|
|
5838
5936
|
"lint-staged",
|
|
5839
|
-
"zirka"
|
|
5937
|
+
"zirka",
|
|
5938
|
+
"jsdom",
|
|
5939
|
+
"@vitejs/plugin-react",
|
|
5940
|
+
"@testing-library/react",
|
|
5941
|
+
"@testing-library/dom"
|
|
5840
5942
|
];
|
|
5841
|
-
function
|
|
5943
|
+
function memberName4(member) {
|
|
5842
5944
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5843
5945
|
}
|
|
5844
5946
|
function sectionPackages(root, section) {
|
|
5845
|
-
const member = root.type === "Object" ? root.members.find((entry) =>
|
|
5947
|
+
const member = root.type === "Object" ? root.members.find((entry) => memberName4(entry) === section) : void 0;
|
|
5846
5948
|
if (member?.value.type !== "Object") return /* @__PURE__ */ new Set();
|
|
5847
|
-
return new Set(member.value.members.map(
|
|
5949
|
+
return new Set(member.value.members.map(memberName4));
|
|
5848
5950
|
}
|
|
5849
5951
|
var nextjsStackRule = {
|
|
5850
5952
|
meta: {
|
|
@@ -5880,19 +5982,151 @@ var nextjsStackRule = {
|
|
|
5880
5982
|
}
|
|
5881
5983
|
};
|
|
5882
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
|
+
|
|
5883
6115
|
// eslint/rules/package-json/index.ts
|
|
5884
6116
|
var repoPackageJsonRules = {
|
|
5885
6117
|
"no-vulyk-dependency": noVulykDependencyRule,
|
|
5886
|
-
"exact-version": exactVersionRule
|
|
6118
|
+
"exact-version": exactVersionRule,
|
|
6119
|
+
"lint-setup": lintSetupRule,
|
|
6120
|
+
"vitest-coverage": vitestCoverageRule
|
|
5887
6121
|
};
|
|
5888
6122
|
var nextjsPackageJsonRules = {
|
|
5889
6123
|
"nextjs-stack": nextjsStackRule
|
|
5890
6124
|
};
|
|
5891
6125
|
|
|
5892
6126
|
// eslint/rules/husky/husky-hook.ts
|
|
5893
|
-
import { existsSync as
|
|
5894
|
-
import
|
|
5895
|
-
function
|
|
6127
|
+
import { existsSync as existsSync3, readFileSync as readFileSync10 } from "fs";
|
|
6128
|
+
import path48 from "path";
|
|
6129
|
+
function memberName6(member) {
|
|
5896
6130
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5897
6131
|
}
|
|
5898
6132
|
var huskyHookRule = {
|
|
@@ -5900,7 +6134,7 @@ var huskyHookRule = {
|
|
|
5900
6134
|
schema: [],
|
|
5901
6135
|
type: "problem",
|
|
5902
6136
|
docs: {
|
|
5903
|
-
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."
|
|
5904
6138
|
}
|
|
5905
6139
|
},
|
|
5906
6140
|
create(context) {
|
|
@@ -5908,29 +6142,39 @@ var huskyHookRule = {
|
|
|
5908
6142
|
Document(node) {
|
|
5909
6143
|
const root = node.body;
|
|
5910
6144
|
if (root.type !== "Object") return;
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
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)) {
|
|
5915
6148
|
context.report({
|
|
5916
6149
|
node,
|
|
5917
6150
|
message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
|
|
5918
6151
|
});
|
|
5919
6152
|
return;
|
|
5920
6153
|
}
|
|
5921
|
-
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
|
+
};
|
|
5922
6165
|
if (!content.includes("lint-staged")) {
|
|
5923
6166
|
context.report({ node, message: ".husky/pre-commit must run lint-staged." });
|
|
5924
6167
|
}
|
|
5925
|
-
|
|
5926
|
-
context.report({ node, message: ".husky/pre-commit must run npm run typecheck." });
|
|
5927
|
-
}
|
|
6168
|
+
requireNamedScript("typecheck");
|
|
5928
6169
|
if (!content.includes("libyear --limit-major-individual=1")) {
|
|
5929
6170
|
context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
|
|
5930
6171
|
}
|
|
5931
|
-
const
|
|
6172
|
+
const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
|
|
6173
|
+
if (existsSync3(suppressionsPath)) {
|
|
6174
|
+
requireNamedScript("lint:prune");
|
|
6175
|
+
}
|
|
5932
6176
|
if (scripts?.value.type === "Object") {
|
|
5933
|
-
const prepare = scripts.value.members.find((member) =>
|
|
6177
|
+
const prepare = scripts.value.members.find((member) => memberName6(member) === "prepare");
|
|
5934
6178
|
if (prepare?.value.type === "String" && !prepare.value.value.includes("husky")) {
|
|
5935
6179
|
context.report({
|
|
5936
6180
|
node: prepare,
|
|
@@ -5949,8 +6193,8 @@ var huskyRules = {
|
|
|
5949
6193
|
};
|
|
5950
6194
|
|
|
5951
6195
|
// eslint/rules/vulyk/vulyk-docs.ts
|
|
5952
|
-
import { existsSync as
|
|
5953
|
-
import
|
|
6196
|
+
import { existsSync as existsSync4, readFileSync as readFileSync11 } from "fs";
|
|
6197
|
+
import path49 from "path";
|
|
5954
6198
|
var PASIKA_REPO = "Bredansky/pasika";
|
|
5955
6199
|
var BASE_REQUIRED_DOCS = [
|
|
5956
6200
|
{ name: "documentation-guide", path: "docs/documentation-guide" },
|
|
@@ -5961,13 +6205,13 @@ var NEXTJS_REQUIRED_DOCS = [
|
|
|
5961
6205
|
{ name: "next-codebase-guide", path: "docs/next-codebase-guide" },
|
|
5962
6206
|
{ name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
|
|
5963
6207
|
];
|
|
5964
|
-
function
|
|
6208
|
+
function memberName7(member) {
|
|
5965
6209
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
5966
6210
|
}
|
|
5967
6211
|
function hasDependency(root, name) {
|
|
5968
|
-
const section = root.members.find((member) =>
|
|
6212
|
+
const section = root.members.find((member) => memberName7(member) === "dependencies");
|
|
5969
6213
|
if (section?.value.type !== "Object") return false;
|
|
5970
|
-
return section.value.members.some((member) =>
|
|
6214
|
+
return section.value.members.some((member) => memberName7(member) === name);
|
|
5971
6215
|
}
|
|
5972
6216
|
var vulykDocsRule = {
|
|
5973
6217
|
meta: {
|
|
@@ -5983,16 +6227,16 @@ var vulykDocsRule = {
|
|
|
5983
6227
|
if (!context.filename.endsWith("package.json")) return;
|
|
5984
6228
|
const root = node.body;
|
|
5985
6229
|
if (root.type !== "Object") return;
|
|
5986
|
-
const projectRoot =
|
|
5987
|
-
const configPath =
|
|
5988
|
-
if (!
|
|
6230
|
+
const projectRoot = path49.dirname(path49.resolve(context.filename));
|
|
6231
|
+
const configPath = path49.join(projectRoot, "vulyk.config.ts");
|
|
6232
|
+
if (!existsSync4(configPath)) {
|
|
5989
6233
|
context.report({
|
|
5990
6234
|
node,
|
|
5991
6235
|
message: "No vulyk.config.ts found. Run npx vulyk@latest init to create one that tracks the framework's docs."
|
|
5992
6236
|
});
|
|
5993
6237
|
return;
|
|
5994
6238
|
}
|
|
5995
|
-
const config =
|
|
6239
|
+
const config = readFileSync11(configPath, "utf8");
|
|
5996
6240
|
if (!config.includes(PASIKA_REPO)) {
|
|
5997
6241
|
context.report({
|
|
5998
6242
|
node,
|
|
@@ -6009,8 +6253,8 @@ var vulykDocsRule = {
|
|
|
6009
6253
|
});
|
|
6010
6254
|
}
|
|
6011
6255
|
}
|
|
6012
|
-
const agentsPath =
|
|
6013
|
-
if (!
|
|
6256
|
+
const agentsPath = path49.join(projectRoot, "AGENTS.md");
|
|
6257
|
+
if (!existsSync4(agentsPath)) {
|
|
6014
6258
|
context.report({
|
|
6015
6259
|
node,
|
|
6016
6260
|
message: "No AGENTS.md found. Run npx vulyk@latest agents to generate the agent file that routes to the tracked docs."
|
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
|
},
|