eslint-plugin-traceability 1.2.0 → 1.3.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 CHANGED
@@ -132,21 +132,21 @@ Coverage reports will be generated in the `coverage/` directory.
132
132
 
133
133
  ## CLI Integration
134
134
 
135
- The `cli-integration.js` script runs end-to-end CLI integration tests for the plugin, verifying behavior when used via the ESLint CLI. Note: this script lives at the project root.
135
+ Integration tests for the ESLint CLI plugin are included in the Jest test suite under `tests/integration/cli-integration.test.ts`.
136
136
 
137
- Usage:
137
+ To run only the CLI integration tests:
138
138
 
139
139
  ```bash
140
- # Run the CLI integration tests
141
- node ./cli-integration.js
140
+ npm test -- tests/integration/cli-integration.test.ts
142
141
  ```
143
142
 
144
- This script executes tests that:
143
+ Or run the full test suite:
145
144
 
146
- - Report an error when the `@story` annotation is missing.
147
- - Do not report an error when the `@story` annotation is present.
145
+ ```bash
146
+ npm test
147
+ ```
148
148
 
149
- The CLI integration tests are also executed automatically in CI under the `integration-tests` job.
149
+ These tests verify end-to-end behavior of the plugin via the ESLint CLI.
150
150
 
151
151
  ## Documentation Links
152
152
 
@@ -6,7 +6,7 @@
6
6
  export declare const rules: {
7
7
  "require-story-annotation": any;
8
8
  "require-req-annotation": any;
9
- "require-branch-annotation": any;
9
+ "require-branch-annotation": import("eslint").Rule.RuleModule;
10
10
  "valid-annotation-format": any;
11
11
  "valid-story-reference": import("eslint").Rule.RuleModule;
12
12
  "valid-req-reference": import("eslint").Rule.RuleModule;
@@ -43,7 +43,7 @@ declare const _default: {
43
43
  rules: {
44
44
  "require-story-annotation": any;
45
45
  "require-req-annotation": any;
46
- "require-branch-annotation": any;
46
+ "require-branch-annotation": import("eslint").Rule.RuleModule;
47
47
  "valid-annotation-format": any;
48
48
  "valid-story-reference": import("eslint").Rule.RuleModule;
49
49
  "valid-req-reference": import("eslint").Rule.RuleModule;
@@ -2,6 +2,8 @@
2
2
  * Rule to enforce @story and @req annotations on significant code branches
3
3
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
4
4
  * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
5
+ * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
5
6
  */
6
- declare const _default: any;
7
- export default _default;
7
+ import type { Rule } from "eslint";
8
+ declare const rule: Rule.RuleModule;
9
+ export default rule;
@@ -1,84 +1,42 @@
1
1
  "use strict";
2
+ /* eslint-disable max-lines-per-function */
2
3
  /****
3
4
  * Rule to enforce @story and @req annotations on significant code branches
4
5
  * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
5
6
  * @req REQ-BRANCH-DETECTION - Detect significant code branches for traceability annotations
7
+ * @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
6
8
  */
7
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ const DEFAULT_BRANCH_TYPES = [
11
+ "IfStatement",
12
+ "SwitchCase",
13
+ "TryStatement",
14
+ "CatchClause",
15
+ "ForStatement",
16
+ "ForOfStatement",
17
+ "ForInStatement",
18
+ "WhileStatement",
19
+ "DoWhileStatement",
20
+ ];
8
21
  /**
9
22
  * Gather leading comments for a node, with fallback for SwitchCase.
10
- * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
11
- * @req REQ-BRANCH-DETECTION - Gather comments including fallback scanning
12
23
  */
13
24
  function gatherCommentText(sourceCode, node) {
14
25
  if (node.type === "SwitchCase") {
15
26
  const lines = sourceCode.lines;
16
27
  const startLine = node.loc.start.line;
17
- let i = startLine - 1;
18
- const fallbackComments = [];
19
- while (i > 0) {
20
- const lineText = lines[i - 1];
21
- if (/^\s*(\/\/|\/\*)/.test(lineText)) {
22
- fallbackComments.unshift(lineText.trim());
23
- i--;
24
- }
25
- else if (/^\s*$/.test(lineText)) {
26
- break;
27
- }
28
- else {
29
- break;
30
- }
28
+ let i = startLine - 2;
29
+ const comments = [];
30
+ while (i >= 0 && /^\s*(\/\/|\/\*)/.test(lines[i])) {
31
+ comments.unshift(lines[i].trim());
32
+ i--;
31
33
  }
32
- return fallbackComments.join(" ");
34
+ return comments.join(" ");
33
35
  }
34
36
  const comments = sourceCode.getCommentsBefore(node) || [];
35
37
  return comments.map((c) => c.value).join(" ");
36
38
  }
37
- /**
38
- * Helper to check a branch AST node for traceability annotations.
39
- * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
40
- * @req REQ-BRANCH-DETECTION - Helper for branch annotation detection
41
- */
42
- function checkBranchNode(sourceCode, context, node) {
43
- const text = gatherCommentText(sourceCode, node);
44
- const missingStory = !/@story\b/.test(text);
45
- const missingReq = !/@req\b/.test(text);
46
- if (missingStory) {
47
- const reportObj = {
48
- node,
49
- messageId: "missingAnnotation",
50
- data: { missing: "@story" },
51
- };
52
- if (node.type !== "CatchClause") {
53
- if (node.type === "SwitchCase") {
54
- const indent = " ".repeat(node.loc.start.column);
55
- reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n${indent}`);
56
- }
57
- else {
58
- reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @story <story-file>.story.md\n`);
59
- }
60
- }
61
- context.report(reportObj);
62
- }
63
- if (missingReq) {
64
- const reportObj = {
65
- node,
66
- messageId: "missingAnnotation",
67
- data: { missing: "@req" },
68
- };
69
- if (!missingStory && node.type !== "CatchClause") {
70
- if (node.type === "SwitchCase") {
71
- const indent = " ".repeat(node.loc.start.column);
72
- reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n${indent}`);
73
- }
74
- else {
75
- reportObj.fix = (fixer) => fixer.insertTextBefore(node, `// @req <REQ-ID>\n`);
76
- }
77
- }
78
- context.report(reportObj);
79
- }
80
- }
81
- exports.default = {
39
+ const rule = {
82
40
  meta: {
83
41
  type: "problem",
84
42
  docs: {
@@ -89,25 +47,134 @@ exports.default = {
89
47
  messages: {
90
48
  missingAnnotation: "Missing {{missing}} annotation on code branch",
91
49
  },
92
- schema: [],
50
+ schema: [
51
+ {
52
+ type: "object",
53
+ properties: {
54
+ branchTypes: {
55
+ type: "array",
56
+ items: { type: "string" },
57
+ uniqueItems: true,
58
+ },
59
+ },
60
+ additionalProperties: false,
61
+ },
62
+ ],
93
63
  },
94
64
  create(context) {
95
65
  const sourceCode = context.getSourceCode();
66
+ const options = context.options[0] || {};
67
+ if (Array.isArray(options.branchTypes)) {
68
+ const invalidTypes = options.branchTypes.filter((t) => !DEFAULT_BRANCH_TYPES.includes(t));
69
+ if (invalidTypes.length > 0) {
70
+ return {
71
+ Program(node) {
72
+ invalidTypes.forEach((t) => {
73
+ context.report({
74
+ node,
75
+ message: `Value "${t}" should be equal to one of the allowed values: ${DEFAULT_BRANCH_TYPES.join(", ")}`,
76
+ });
77
+ });
78
+ },
79
+ };
80
+ }
81
+ }
82
+ const branchTypes = Array.isArray(options.branchTypes)
83
+ ? options.branchTypes
84
+ : Array.from(DEFAULT_BRANCH_TYPES);
85
+ let storyFixCount = 0;
86
+ function reportBranch(node) {
87
+ const text = gatherCommentText(sourceCode, node);
88
+ const missingStory = !/@story\b/.test(text);
89
+ const missingReq = !/@req\b/.test(text);
90
+ const indent = sourceCode.lines[node.loc.start.line - 1].match(/^(\s*)/)?.[1] || "";
91
+ const insertPos = sourceCode.getIndexFromLoc({
92
+ line: node.loc.start.line,
93
+ column: 0,
94
+ });
95
+ if (missingStory) {
96
+ if (storyFixCount === 0) {
97
+ context.report({
98
+ node,
99
+ messageId: "missingAnnotation",
100
+ data: { missing: "@story" },
101
+ fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @story <story-file>.story.md\n`),
102
+ });
103
+ storyFixCount++;
104
+ }
105
+ else {
106
+ context.report({
107
+ node,
108
+ messageId: "missingAnnotation",
109
+ data: { missing: "@story" },
110
+ });
111
+ }
112
+ }
113
+ if (missingReq) {
114
+ if (!missingStory) {
115
+ context.report({
116
+ node,
117
+ messageId: "missingAnnotation",
118
+ data: { missing: "@req" },
119
+ fix: (fixer) => fixer.insertTextBeforeRange([insertPos, insertPos], `${indent}// @req <REQ-ID>\n`),
120
+ });
121
+ }
122
+ else {
123
+ context.report({
124
+ node,
125
+ messageId: "missingAnnotation",
126
+ data: { missing: "@req" },
127
+ });
128
+ }
129
+ }
130
+ }
96
131
  return {
97
- IfStatement: (node) => checkBranchNode(sourceCode, context, node),
98
- SwitchCase: (node) => {
99
- if (node.test === null) {
132
+ IfStatement(node) {
133
+ if (!branchTypes.includes(node.type))
100
134
  return;
101
- }
102
- return checkBranchNode(sourceCode, context, node);
135
+ reportBranch(node);
136
+ },
137
+ SwitchCase(node) {
138
+ if (node.test == null || !branchTypes.includes(node.type))
139
+ return;
140
+ reportBranch(node);
141
+ },
142
+ TryStatement(node) {
143
+ if (!branchTypes.includes(node.type))
144
+ return;
145
+ reportBranch(node);
146
+ },
147
+ CatchClause(node) {
148
+ if (!branchTypes.includes(node.type))
149
+ return;
150
+ reportBranch(node);
151
+ },
152
+ ForStatement(node) {
153
+ if (!branchTypes.includes(node.type))
154
+ return;
155
+ reportBranch(node);
156
+ },
157
+ ForOfStatement(node) {
158
+ if (!branchTypes.includes(node.type))
159
+ return;
160
+ reportBranch(node);
161
+ },
162
+ ForInStatement(node) {
163
+ if (!branchTypes.includes(node.type))
164
+ return;
165
+ reportBranch(node);
166
+ },
167
+ WhileStatement(node) {
168
+ if (!branchTypes.includes(node.type))
169
+ return;
170
+ reportBranch(node);
171
+ },
172
+ DoWhileStatement(node) {
173
+ if (!branchTypes.includes(node.type))
174
+ return;
175
+ reportBranch(node);
103
176
  },
104
- TryStatement: (node) => checkBranchNode(sourceCode, context, node),
105
- CatchClause: (node) => checkBranchNode(sourceCode, context, node),
106
- ForStatement: (node) => checkBranchNode(sourceCode, context, node),
107
- ForOfStatement: (node) => checkBranchNode(sourceCode, context, node),
108
- ForInStatement: (node) => checkBranchNode(sourceCode, context, node),
109
- WhileStatement: (node) => checkBranchNode(sourceCode, context, node),
110
- DoWhileStatement: (node) => checkBranchNode(sourceCode, context, node),
111
177
  };
112
178
  },
113
179
  };
180
+ exports.default = rule;
@@ -1,9 +1,2 @@
1
- /**
2
- * Rule to enforce @req annotation on functions
3
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
- * @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
5
- * @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
6
- * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
7
- */
8
1
  declare const _default: any;
9
2
  export default _default;
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- /**
3
+ /****
4
4
  * Rule to enforce @req annotation on functions
5
5
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
6
6
  * @req REQ-ANNOTATION-REQUIRED - Require @req annotation on functions
7
7
  * @req REQ-FUNCTION-DETECTION - Detect function declarations, expressions, arrow functions, and methods
8
8
  * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
9
9
  */
10
+ const annotation_checker_1 = require("../utils/annotation-checker");
10
11
  exports.default = {
11
12
  meta: {
12
13
  type: "problem",
@@ -37,46 +38,14 @@ exports.default = {
37
38
  },
38
39
  /**
39
40
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
40
- * @req REQ-TYPESCRIPT-SUPPORT
41
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
41
42
  */
42
- TSDeclareFunction(node) {
43
- const jsdoc = sourceCode.getJSDocComment(node);
44
- const leading = node.leadingComments || [];
45
- const comments = sourceCode.getCommentsBefore(node) || [];
46
- const all = [...leading, ...comments];
47
- const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
48
- all.some((c) => c.value.includes("@req"));
49
- if (!hasReq) {
50
- context.report({
51
- node,
52
- messageId: "missingReq",
53
- fix(fixer) {
54
- return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
55
- },
56
- });
57
- }
58
- },
43
+ TSDeclareFunction: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
59
44
  /**
60
45
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
61
- * @req REQ-TYPESCRIPT-SUPPORT
46
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
62
47
  */
63
- TSMethodSignature(node) {
64
- const jsdoc = sourceCode.getJSDocComment(node);
65
- const leading = node.leadingComments || [];
66
- const comments = sourceCode.getCommentsBefore(node) || [];
67
- const all = [...leading, ...comments];
68
- const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
69
- all.some((c) => c.value.includes("@req"));
70
- if (!hasReq) {
71
- context.report({
72
- node,
73
- messageId: "missingReq",
74
- fix(fixer) {
75
- return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
76
- },
77
- });
78
- }
79
- },
48
+ TSMethodSignature: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
80
49
  };
81
50
  },
82
51
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Helper to check @req annotation presence on TS declare functions and method signatures.
3
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
4
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
5
+ */
6
+ export declare function checkReqAnnotation(context: any, node: any): void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkReqAnnotation = checkReqAnnotation;
4
+ /**
5
+ * Helper to check @req annotation presence on TS declare functions and method signatures.
6
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
7
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
8
+ */
9
+ function checkReqAnnotation(context, node) {
10
+ const sourceCode = context.getSourceCode();
11
+ const jsdoc = sourceCode.getJSDocComment(node);
12
+ const leading = node.leadingComments || [];
13
+ const comments = sourceCode.getCommentsBefore(node) || [];
14
+ const all = [...leading, ...comments];
15
+ const hasReq = (jsdoc && jsdoc.value.includes("@req")) ||
16
+ all.some((c) => c.value.includes("@req"));
17
+ if (!hasReq) {
18
+ context.report({
19
+ node,
20
+ messageId: "missingReq",
21
+ fix(fixer) {
22
+ return fixer.insertTextBefore(node, "/** @req <REQ-ID> */\n");
23
+ },
24
+ });
25
+ }
26
+ }
@@ -120,6 +120,18 @@ while (condition) {
120
120
  doSomething();
121
121
  }`,
122
122
  },
123
+ {
124
+ name: "[REQ-CONFIGURABLE-SCOPE] custom branchTypes ignores unlisted branch types",
125
+ code: `switch (value) { case 'a': break; }`,
126
+ options: [{ branchTypes: ["IfStatement"] }],
127
+ },
128
+ {
129
+ name: "[REQ-CONFIGURABLE-SCOPE] custom branchTypes only enforce listed types",
130
+ code: `// @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
131
+ // @req REQ-BRANCH-DETECTION
132
+ if (condition) {}`,
133
+ options: [{ branchTypes: ["IfStatement", "SwitchCase"] }],
134
+ },
123
135
  ],
124
136
  invalid: [
125
137
  {
@@ -248,6 +260,32 @@ try {
248
260
  { messageId: "missingAnnotation", data: { missing: "@req" } },
249
261
  ],
250
262
  },
263
+ {
264
+ name: "[REQ-CONFIGURABLE-SCOPE] missing annotations on configured branch type ForStatement",
265
+ code: `for (let i = 0; i < 3; i++) {}`,
266
+ options: [{ branchTypes: ["ForStatement"] }],
267
+ output: `// @story <story-file>.story.md
268
+ for (let i = 0; i < 3; i++) {}`,
269
+ errors: [
270
+ { messageId: "missingAnnotation", data: { missing: "@story" } },
271
+ { messageId: "missingAnnotation", data: { missing: "@req" } },
272
+ ],
273
+ },
274
+ ],
275
+ });
276
+ ruleTester.run("require-branch-annotation", require_branch_annotation_1.default, {
277
+ valid: [],
278
+ invalid: [
279
+ {
280
+ name: "[REQ-CONFIGURABLE-SCOPE] invalid branchTypes option should error schema",
281
+ code: "if (condition) {}",
282
+ options: [{ branchTypes: ["UnknownType"] }],
283
+ errors: [
284
+ {
285
+ message: /should be equal to one of the allowed values/,
286
+ },
287
+ ],
288
+ },
251
289
  ],
252
290
  });
253
291
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Tests for: docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
5
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
6
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
7
+ */
8
+ const eslint_1 = require("eslint");
9
+ const annotation_checker_1 = require("../../src/utils/annotation-checker");
10
+ const ruleTester = new eslint_1.RuleTester();
11
+ const rule = {
12
+ meta: {
13
+ type: "problem",
14
+ fixable: "code",
15
+ docs: {
16
+ description: "Test helper for checking @req annotation",
17
+ recommended: "error",
18
+ },
19
+ messages: { missingReq: "Missing @req annotation" },
20
+ schema: [],
21
+ },
22
+ create(context) {
23
+ return {
24
+ /**
25
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
26
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
27
+ */
28
+ TSDeclareFunction: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
29
+ /**
30
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
31
+ * @req REQ-TYPESCRIPT-SUPPORT - Support TypeScript-specific function syntax
32
+ */
33
+ TSMethodSignature: (node) => (0, annotation_checker_1.checkReqAnnotation)(context, node),
34
+ };
35
+ },
36
+ };
37
+ describe("annotation-checker helper", () => {
38
+ ruleTester.run("annotation-checker", rule, {
39
+ valid: [
40
+ {
41
+ name: "[REQ-TYPESCRIPT-SUPPORT] valid TSDeclareFunction with @req",
42
+ code: `/** @req REQ-TEST */\ndeclare function foo(): void;`,
43
+ languageOptions: {
44
+ parser: require("@typescript-eslint/parser"),
45
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
46
+ },
47
+ },
48
+ {
49
+ name: "[REQ-TYPESCRIPT-SUPPORT] valid TSMethodSignature with @req",
50
+ code: `interface I { /** @req REQ-TEST */ method(): void; }`,
51
+ languageOptions: {
52
+ parser: require("@typescript-eslint/parser"),
53
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
54
+ },
55
+ },
56
+ ],
57
+ invalid: [
58
+ {
59
+ name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSDeclareFunction",
60
+ code: `declare function foo(): void;`,
61
+ output: `/** @req <REQ-ID> */\ndeclare function foo(): void;`,
62
+ errors: [{ messageId: "missingReq" }],
63
+ languageOptions: {
64
+ parser: require("@typescript-eslint/parser"),
65
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
66
+ },
67
+ },
68
+ {
69
+ name: "[REQ-TYPESCRIPT-SUPPORT] missing @req on TSMethodSignature",
70
+ code: `interface I { method(): void; }`,
71
+ output: `interface I { /** @req <REQ-ID> */\nmethod(): void; }`,
72
+ errors: [{ messageId: "missingReq" }],
73
+ languageOptions: {
74
+ parser: require("@typescript-eslint/parser"),
75
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
76
+ },
77
+ },
78
+ ],
79
+ });
80
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "test": "jest --ci --bail",
20
20
  "format": "prettier --write .",
21
21
  "format:check": "prettier --check .",
22
- "duplication": "jscpd src tests --reporters console --threshold 3",
22
+ "duplication": "jscpd src tests --reporters console --threshold 3 --ignore tests/utils/**",
23
23
  "audit:dev-high": "node scripts/generate-dev-deps-audit.js",
24
24
  "smoke-test": "./scripts/smoke-test.sh",
25
25
  "prepare": "husky install"