@systemfsoftware/stryker-js-instrumenter 3.0.1 → 5.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # @systemfsoftware/stryker-js-instrumenter
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - The packages are renamed. `plugin-api` is now `@systemfsoftware/stryker-js`, the
8
+ language every plugin is written against. `mutation-run` is split: the run
9
+ itself is `@systemfsoftware/stryker-js-engine`
10
+ (host-neutral, no Node on its manifest) and the Node process entries are
11
+ `@systemfsoftware/stryker-js-cli`, which owns the worker files and the runtime
12
+ gate. `mutation-report` is now `@systemfsoftware/stryker-js-html-reporter`.
13
+ `@systemfsoftware/stryker-js-platform-node` is never published — do not install
14
+ it. Install the new names and change your imports.
15
+
16
+ Options types moved. `StrykerOptions`, `PartialStrykerOptions` and `LogLevel` are
17
+ imported from the `Schema` export; `Mutant`, `MutantStatus`, `Position` and
18
+ `Location` from the `Mutant` export. Point a config's `extends` at the language
19
+ package's `Schema` export.
20
+
21
+ `MutantStatus` accepts one spelling per outcome: `Killed`, `Survived`,
22
+ `NoCoverage`, `Timeout`, `CompileError`, `RuntimeError`, `Ignored` and `Pending`.
23
+ The lowercase and abbreviated forms — `killed`, `timedOut`, `noCoverage` and the
24
+ rest — are gone. A comparison against a removed spelling never matched the value
25
+ the reporter actually produced, so check any status comparison you wrote.
26
+
27
+ Statuses, plugin kinds, exit classes and AST formats are string literal unions
28
+ rather than enums, so read them as their string values. Member access such as
29
+ `ExitClass.VerdictFail` no longer resolves.
30
+
31
+ A plugin no longer receives a logger, and the logger port is gone. Plugins log
32
+ through Effect, and the host decides where that output goes.
33
+
34
+ The bundled base preset is gone. A config inherits from the language package's
35
+ `Schema` export and states the thresholds, reporters and plugins it wants; you no
36
+ longer silently inherit a package manager, a plugin list or a break threshold.
37
+
38
+ ### Patch Changes
39
+
40
+ - Peer Effect requirement advances to 4.0.0-rc.112. No API changes.
41
+
42
+ - The shared helpers package is gone. Nothing installs it any more, and the
43
+ handful of helpers worth sharing now live in the plugin contract next to the
44
+ types they serve:
45
+
46
+ - `strykerReportBugUrl`, `normalizeFileName`, `propertyPath`, `errorToString`
47
+ and `isErrnoException` from `@systemfsoftware/stryker-js/core`
48
+ - `noopLogger` from `@systemfsoftware/stryker-js/logging`
49
+ - `testFilesProvided` from `@systemfsoftware/stryker-js/test-runner`
50
+
51
+ If you imported any of those, change the specifier. Everything else it exported
52
+ had no consumer and is removed: use `Predicate.isNotNullish` from Effect in place
53
+ of `notEmpty`, and `RegExp.escape` in place of `escapeRegExp`.
54
+
55
+ - These packages no longer install dependencies they never imported, so installing them pulls less into your tree.
56
+
57
+ `tslib` is gone from all six. The mutation runner additionally stops installing `lodash.groupby`, `semver` and `source-map`, and the command line interface stops installing `@effect/platform-node-shared`. Nothing exported changes.
58
+
59
+ - Published packages no longer carry build artifacts left over from earlier builds. One package was shipping about a megabyte of bundled test-runner internals this way.
60
+
61
+ - Updated dependencies:
62
+ - @systemfsoftware/stryker-js@2.0.0
63
+
64
+ ## 4.0.0
65
+
66
+ ### Major Changes
67
+
68
+ - instrument() keeps its public signature; the decision moved to a plain kernel, and the instrument workflow now distinguishes in-place from ephemeral output
69
+
70
+ ### Patch Changes
71
+
72
+ - Updated dependencies:
73
+ - @systemfsoftware/effect-cell-types@6.0.0
74
+ - @systemfsoftware/stryker-js@1.0.0
75
+
3
76
  ## 3.0.1
4
77
 
5
78
  ### Patch Changes
package/README.md CHANGED
@@ -18,4 +18,4 @@ The instrumenter is used internally by the Stryker mutation testing framework to
18
18
 
19
19
  ## License
20
20
 
21
- Apache-2.0. Part of [systemfsoftware](https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/testing/mutation/stryker-js/instrumenter#readme).
21
+ Apache-2.0. Part of [systemfsoftware](https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/stryker-js/stryker-js-instrumenter#readme).
package/dist/index.d.mts CHANGED
@@ -1,11 +1,10 @@
1
- import { Wire, Workflow } from "@systemfsoftware/effect-cell-types";
2
1
  import { FileDescription, Mutant } from "@systemfsoftware/stryker-js/Mutant";
3
2
  import * as Effect from "effect/Effect";
4
- import "effect/Result";
3
+ import { Wire } from "@systemfsoftware/effect-cell-types";
5
4
  import * as S from "effect/Schema";
6
5
  import { IgnorerService } from "@systemfsoftware/stryker-js/Ignorer";
7
6
  import "effect/Option";
8
- //#region src/Instrument.workflow.d.ts
7
+ //#region src/Instrument.schema.d.ts
9
8
  declare const InstrumentError_base: S.Class<InstrumentError, S.TaggedStruct<"InstrumentError", {
10
9
  readonly message: S.String;
11
10
  readonly cause: S.Defect;
package/dist/index.mjs CHANGED
@@ -1,9 +1,7 @@
1
- import { Cell, Wire, Workflow } from "@systemfsoftware/effect-cell-types";
2
1
  import { INSTRUMENTER_CONSTANTS, Mutant } from "@systemfsoftware/stryker-js/Mutant";
3
2
  import * as Effect from "effect/Effect";
4
- import { pipe } from "effect/Function";
5
3
  import * as Predicate from "effect/Predicate";
6
- import * as Result from "effect/Result";
4
+ import { Wire } from "@systemfsoftware/effect-cell-types";
7
5
  import * as S from "effect/Schema";
8
6
  import { parseSync } from "oxc-parser";
9
7
  import path from "path";
@@ -12,17 +10,7 @@ import { propertyPath, strykerReportBugUrl } from "@systemfsoftware/stryker-js/S
12
10
  import * as Option from "effect/Option";
13
11
  import path$1 from "node:path";
14
12
  import { RegExpParser, visitRegExpAST } from "@eslint-community/regexpp";
15
- //#region src/Instrument.workflow.ts
16
- /**
17
- * Instrument.workflow — pure decision for the instrument capability.
18
- *
19
- * The heavy lifting (AST traversal, mutant collection, code generation)
20
- * happens in `Instrument.ts`'s read phase, which is allowed to be stateful.
21
- * This decision receives the already-collected mutants as data and wraps them
22
- * into the decision type. All helpers and schemas live in this file so the
23
- * decision body only reaches parameters, const locals, same-file declarations,
24
- * and the sealed pure effect surface.
25
- */
13
+ //#region src/Instrument.schema.ts
26
14
  var InstrumentError = class extends S.TaggedError("@systemfsoftware/stryker-js-instrumenter/InstrumentError")("InstrumentError", {
27
15
  message: S.String,
28
16
  cause: S.Defect()
@@ -72,11 +60,6 @@ var InstrumentResult = class extends S.TaggedClass()("InstrumentResult", {
72
60
  files: S.Array(FileSchema),
73
61
  mutants: S.Array(Mutant)
74
62
  }) {};
75
- const instrumentWorkflow = Workflow.make(InstrumentDecoded, (decoded) => Result.succeed(InstrumentDecision.make({
76
- files: decoded.files,
77
- mutants: decoded.mutants,
78
- asts: decoded.asts
79
- })));
80
63
  S.Literals([
81
64
  "html",
82
65
  "js",
@@ -3988,19 +3971,9 @@ const PredicateRecord = { key: (node) => Predicate.hasProperty(node, "key") ? no
3988
3971
  function isTypeNode(path) {
3989
3972
  return flowTypeAnnotationNodeTypes.includes(path.node.type) || tsTypeAnnotationNodeTypes.includes(path.node.type) || isDeclareVariableStatement(path.node) || isDeclareModule(path.node);
3990
3973
  }
3991
- /**
3992
- * Determines whether or not it is a declare variable statement node.
3993
- * @example
3994
- * declare const foo: 'foo';
3995
- */
3996
3974
  function isDeclareVariableStatement(node) {
3997
3975
  return nodeType(node) === "VariableDeclaration" && "declare" in node && node.declare === true;
3998
3976
  }
3999
- /**
4000
- * Determines whether or not a node is a string literal that is the name of a module.
4001
- * @example
4002
- * declare module "express" {};
4003
- */
4004
3977
  function isDeclareModule(node) {
4005
3978
  return nodeType(node) === "TSModuleDeclaration" && "declare" in node && node.declare === true;
4006
3979
  }
@@ -4032,30 +4005,15 @@ const flowTypeAnnotationNodeTypes = Object.freeze([
4032
4005
  function isImportDeclaration(path) {
4033
4006
  return nodeType(path.node) === "TSImportEqualsDeclaration" || path.node.type === "ImportDeclaration";
4034
4007
  }
4035
- /**
4036
- * returns syntax for `global.activeMutant === $mutantId`
4037
- * @param mutantId The id of the mutant to switch
4038
- */
4039
4008
  function mutantTestExpression(mutantId) {
4040
4009
  return callExpression(identifier(IS_MUTANT_ACTIVE_HELPER), [stringLiteral(mutantId)]);
4041
4010
  }
4042
- /**
4043
- * Returns a sequence of mutation coverage counters with an optional last expression.
4044
- *
4045
- * @example (global.__coverMutant__(0, 1), 40 + 2)
4046
- * @param mutants The mutants for which covering syntax needs to be generated
4047
- * @param targetExpression The original expression
4048
- */
4049
4011
  function mutationCoverageSequenceExpression(mutants, targetExpression) {
4050
4012
  const mutantIds = [...mutants].map((mutant) => stringLiteral(mutant.id));
4051
4013
  const sequence = [callExpression(identifier(COVER_MUTANT_HELPER), mutantIds)];
4052
4014
  if (targetExpression) sequence.push(targetExpression);
4053
4015
  return sequenceExpression(sequence);
4054
4016
  }
4055
- /**
4056
- * Verifies the placer's claim: `canPlace` established the node kind, so a
4057
- * mismatch here means the placer was handed a mutant it never accepted.
4058
- */
4059
4017
  function nodeOfKind(mutant, node, isKind, kind) {
4060
4018
  if (!isKind(node)) throw new Error(`Cannot place mutant ${mutant.id}: expected ${kind}, got ${node.type}`);
4061
4019
  return node;
@@ -4068,14 +4026,6 @@ function throwPlacementError(error, nodePath, placer, mutants, fileName, lineTab
4068
4026
  const errorMessage = `${location} ${message}. Either remove this file from the list of files to be mutated, or exclude the mutator (using ${propertyPath()("mutator", "excludedMutations")}). Please report this issue at ${strykerReportBugUrl(message)}. Original error: ${error.stack}`;
4069
4027
  throw new Error(errorMessage);
4070
4028
  }
4071
- /**
4072
- * Will set the identifier of anonymous function expressions if is located in a variable declaration.
4073
- * Will treat input as readonly. Returns undefined if not needed.
4074
- * @example
4075
- * const a = function() { }
4076
- * becomes
4077
- * const a = function a() {}
4078
- */
4079
4029
  function classOrFunctionExpressionNamedIfNeeded(path) {
4080
4030
  const kind = nodeType(path.node);
4081
4031
  if ((kind === "FunctionExpression" || kind === "ClassExpression") && !("id" in path.node) === false && path.node["id"] !== null) {}
@@ -4088,14 +4038,6 @@ function classOrFunctionExpressionNamedIfNeeded(path) {
4088
4038
  if (parentPath !== null && parentPath.node.type === "Property" && parentPath.node.key.type === "Identifier" && path.getStatementParent()?.node.type === "VariableDeclaration") return path.node;
4089
4039
  }
4090
4040
  }
4091
- /**
4092
- * Will set the identifier of anonymous arrow function expressions if is located in a variable declaration.
4093
- * Will treat input as readonly. Returns undefined if not needed.
4094
- * @example
4095
- * const a = () => { }
4096
- * becomes
4097
- * const a = (() => { const a = () => {}; return a; })()
4098
- */
4099
4041
  function arrowFunctionExpressionNamedIfNeeded(path) {
4100
4042
  const parentPath = path.parentPath;
4101
4043
  if (path.node.type === "ArrowFunctionExpression" && parentPath !== null && parentPath.node.type === "VariableDeclarator" && parentPath.node.id.type === "Identifier") {
@@ -4123,52 +4065,30 @@ function isValidExpression(path) {
4123
4065
  if (parent === null) return true;
4124
4066
  const parentNode = parent.node;
4125
4067
  return !isObjectPropertyKey() && !isPartOfChain() && !(parentNode.type === "TaggedTemplateExpression") && !isPartOfDeleteExpression() && !isPartOfAssignmentExpression();
4126
- /**
4127
- * Determines if the expression is property of an object.
4128
- * @example
4129
- * const a = {
4130
- * 'foo': 'bar' // 'foo' here is an object property
4131
- * };
4132
- */
4133
4068
  function isObjectPropertyKey() {
4134
4069
  return parentNode.type === "Property" && parentNode.key === path.node;
4135
4070
  }
4136
- /**
4137
- * Determines if the expression is part of a call/member chain.
4138
- * @example
4139
- * // bar is part of chain, foo is NOT part of the chain:
4140
- * foo.bar.baz();
4141
- * foo.bar?.baz()
4142
- * foo.bar;
4143
- * foo.bar!;
4144
- * foo.bar();
4145
- * foo?.bar();
4146
- * baz[foo.bar()]
4147
- * bar?.baz[0]
4148
- */
4149
4071
  function isPartOfChain() {
4150
4072
  return isMemberOrCallOrNonNullExpression(path) && (isMemberExpressionNode(parent) && !(parentNode.type === "MemberExpression" && parentNode.computed && parentNode.property === path.node) || nodeType(parentNode) === "TSNonNullExpression" || isCallExpressionNode(parent) && parentNode.type === "CallExpression" && parentNode.callee === path.node);
4151
4073
  }
4152
- /**
4153
- * Determines if the expression is part of a delete expression.
4154
- * @returns true if the expression is part of a delete expression
4155
- * @example
4156
- * delete foo.bar;
4157
- */
4158
4074
  function isPartOfDeleteExpression() {
4159
4075
  return parentNode.type === "UnaryExpression" && parentNode.operator === "delete";
4160
4076
  }
4161
- /**
4162
- * Determines if the expression is part of an assignment expression.
4163
- * @returns true if the expression is part of an assignment expression
4164
- * @example
4165
- * foo.bar = 42;
4166
- * initialNodes.filter((n) => n.id === 'tiptilt')[0].className = tiptiltState;
4167
- */
4168
4077
  function isPartOfAssignmentExpression() {
4169
4078
  return parentNode.type === "AssignmentExpression" && parentNode.left === path.node;
4170
4079
  }
4171
4080
  }
4081
+ function unwrapParenthesizedExpression(node) {
4082
+ let current = node;
4083
+ while (nodeType(current) === "ParenthesizedExpression") {
4084
+ const wrapper = current;
4085
+ if (typeof wrapper !== "object" || wrapper === null || !("expression" in wrapper)) break;
4086
+ const inner = wrapper.expression;
4087
+ if (inner === void 0 || inner === null) break;
4088
+ current = inner;
4089
+ }
4090
+ return current;
4091
+ }
4172
4092
  const allMutantPlacers = Object.freeze([
4173
4093
  {
4174
4094
  name: "expressionMutantPlacer",
@@ -4178,7 +4098,7 @@ const allMutantPlacers = Object.freeze([
4178
4098
  place(path, appliedMutants) {
4179
4099
  let expression = nameIfAnonymous(path);
4180
4100
  expression = mutationCoverageSequenceExpression(appliedMutants.keys(), expression);
4181
- for (const [mutant, appliedMutant] of appliedMutants) expression = conditionalExpression(mutantTestExpression(mutant.id), nodeOfKind(mutant, appliedMutant, isExpressionKind, "an expression"), expression);
4101
+ for (const [mutant, appliedMutant] of appliedMutants) expression = conditionalExpression(mutantTestExpression(mutant.id), nodeOfKind(mutant, unwrapParenthesizedExpression(appliedMutant), isExpressionKind, "an expression"), expression);
4182
4102
  path.replaceWith(expression);
4183
4103
  }
4184
4104
  },
@@ -4284,11 +4204,6 @@ function isMemberExpressionWithIdentifier(node, objectNames, propertyName) {
4284
4204
  }
4285
4205
  const strykerPlugins = [];
4286
4206
  const frameworkPluginsFileUrl = import.meta.url;
4287
- /**
4288
- * The instrumentation header: parsed once with oxc, frozen, shared. The
4289
- * `globalThis` implementation is based on core-js's — see
4290
- * https://github.com/stryker-mutator/stryker-js/issues/4035
4291
- */
4292
4207
  const parsedInstrumentationHeader = parseSync("instrumenter-header.js", `// @ts-nocheck
4293
4208
  var ${STRYKER_NAMESPACE_HELPER} = function(){
4294
4209
  var g = typeof globalThis === 'object' && globalThis && globalThis.Math === Math && globalThis || new Function("return this")();
@@ -4561,9 +4476,6 @@ function toError(value) {
4561
4476
  }
4562
4477
  //#endregion
4563
4478
  //#region src/Instrument.ts
4564
- /**
4565
- * Instrument — the instrument capability: Cell description, file/mutant types and the instrument entry point.
4566
- */
4567
4479
  const commentDirectiveRegEx = /^(\s*)@(ts-[a-z-]+).*$/;
4568
4480
  const tsDirectiveLikeRegEx = /@(ts-[a-z-]+)/;
4569
4481
  const startingCommentRegex = /(^\s*\/\*.*?\*\/)/gs;
@@ -4707,7 +4619,7 @@ function toTransformerOptions(options) {
4707
4619
  function isAst(value) {
4708
4620
  return typeof value === "object" && value !== null && "format" in value && "root" in value;
4709
4621
  }
4710
- const instrumentDescription = pipe(Cell.read((command) => Effect.gen(function* () {
4622
+ const readCollected = (command) => Effect.gen(function* () {
4711
4623
  const files = command.files;
4712
4624
  const options = command.options;
4713
4625
  const parse = createParser();
@@ -4751,54 +4663,58 @@ const instrumentDescription = pipe(Cell.read((command) => Effect.gen(function* (
4751
4663
  })
4752
4664
  })
4753
4665
  };
4754
- })), Cell.decode((raw) => Result.succeed(InstrumentDecoded.make({
4755
- files: raw.files,
4756
- options: raw.options,
4757
- asts: raw.asts,
4758
- mutants: raw.mutants
4759
- }))), Cell.decide(instrumentWorkflow), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
4760
- onFailure: (error) => Effect.fail(error),
4761
- onSuccess: (decision) => Effect.try({
4762
- try: () => {
4763
- const files = decision.files;
4764
- const asts = decision.asts;
4765
- const outFiles = [];
4766
- const isAstValue = (value) => typeof value === "object" && value !== null && "format" in value && "root" in value;
4767
- for (let i = 0; i < asts.length; i++) {
4768
- const maybeAst = asts[i];
4769
- const maybeFile = files[i];
4770
- if (maybeAst === void 0 || maybeFile === void 0) continue;
4771
- if (!isAstValue(maybeAst)) continue;
4772
- const ast = maybeAst;
4773
- const file = maybeFile;
4774
- const mutatedContent = print(ast);
4775
- outFiles.push({
4776
- name: file.name,
4777
- mutate: file.mutate,
4778
- content: mutatedContent
4779
- });
4780
- }
4781
- return InstrumentResult.make({
4782
- files: outFiles,
4783
- mutants: decision.mutants
4666
+ });
4667
+ const printDecision = (decision) => Effect.try({
4668
+ try: () => {
4669
+ const files = decision.files;
4670
+ const asts = decision.asts;
4671
+ const outFiles = [];
4672
+ for (let i = 0; i < asts.length; i++) {
4673
+ const maybeAst = asts[i];
4674
+ const maybeFile = files[i];
4675
+ if (maybeAst === void 0 || maybeFile === void 0) continue;
4676
+ if (!isAst(maybeAst)) continue;
4677
+ const ast = maybeAst;
4678
+ const file = maybeFile;
4679
+ const mutatedContent = print(ast);
4680
+ outFiles.push({
4681
+ name: file.name,
4682
+ mutate: file.mutate,
4683
+ content: mutatedContent
4784
4684
  });
4785
- },
4786
- catch: (cause) => new InstrumentError({
4787
- message: "Failed to print",
4788
- cause
4789
- })
4685
+ }
4686
+ return InstrumentResult.make({
4687
+ files: outFiles,
4688
+ mutants: decision.mutants
4689
+ });
4690
+ },
4691
+ catch: (cause) => new InstrumentError({
4692
+ message: "Failed to print",
4693
+ cause
4790
4694
  })
4791
- })));
4792
- const instrument = (files, options) => {
4695
+ });
4696
+ const decideInstrument = (decoded) => InstrumentDecision.make({
4697
+ files: decoded.files,
4698
+ mutants: decoded.mutants,
4699
+ asts: decoded.asts
4700
+ });
4701
+ const instrument = (files, options) => Effect.gen(function* () {
4793
4702
  const schemaFiles = files.map((file) => ({
4794
4703
  name: file.name,
4795
4704
  content: file.content,
4796
4705
  mutate: file.mutate
4797
4706
  }));
4798
- return Cell.apply(instrumentDescription, InstrumentCommand.make({
4707
+ const collected = yield* readCollected(InstrumentCommand.make({
4799
4708
  files: schemaFiles,
4800
4709
  options
4801
4710
  }));
4802
- };
4711
+ const decision = decideInstrument(InstrumentDecoded.make({
4712
+ files: collected.files,
4713
+ options: collected.options,
4714
+ asts: collected.asts,
4715
+ mutants: collected.mutants
4716
+ }));
4717
+ return yield* printDecision(decision);
4718
+ });
4803
4719
  //#endregion
4804
4720
  export { angularIgnorer, disableTypeChecks, frameworkPluginsFileUrl, instrument, strykerPlugins };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@systemfsoftware/stryker-js-instrumenter",
3
- "version": "3.0.1",
3
+ "version": "5.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
7
- "directory": "packages/testing/mutation/stryker-js/instrumenter"
7
+ "directory": "packages/stryker-js/stryker-js-instrumenter"
8
8
  },
9
- "homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/testing/mutation/stryker-js/instrumenter#readme",
9
+ "homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/stryker-js/stryker-js-instrumenter#readme",
10
10
  "bugs": "https://github.com/systemfsoftware/systemfsoftware/issues",
11
11
  "description": "The code instrumenter used by the Stryker mutation runner",
12
12
  "type": "module",
@@ -24,8 +24,8 @@
24
24
  "angular-html-parser": "~10.4.0",
25
25
  "effect": "^4.0.0-rc.112",
26
26
  "oxc-parser": "^0.140.0",
27
- "@systemfsoftware/effect-cell-types": "^5.0.2",
28
- "@systemfsoftware/stryker-js": "^0.2.0"
27
+ "@systemfsoftware/effect-cell-types": "^6.0.1",
28
+ "@systemfsoftware/stryker-js": "^2.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
@@ -37,11 +37,11 @@
37
37
  "tsdown": "^0.22.14",
38
38
  "typescript": "^7",
39
39
  "vitest": "^4",
40
- "@systemfsoftware/effect-gherkin-spec": "^4.0.0",
41
- "@systemfsoftware/oxlint-config": "^0.1.0",
40
+ "@systemfsoftware/effect-gherkin-spec": "^4.0.1",
41
+ "@systemfsoftware/all": "^1.1.3",
42
42
  "@systemfsoftware/tsconfig": "^1.3.3",
43
- "@systemfsoftware/vitest-config": "^0.1.0",
44
- "@systemfsoftware/all": "^1.1.0"
43
+ "@systemfsoftware/oxlint-config": "^0.1.0",
44
+ "@systemfsoftware/vitest-config": "^0.1.0"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=20.0.0"