@akshatmittal/invoker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/index.d.mts +79 -0
- package/dist/index.mjs +174 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akshat Mittal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @akshatmittal/invoker
|
|
2
|
+
|
|
3
|
+
A strictly typed TypeScript DSL for matrix-driven regression workflows. Invoker
|
|
4
|
+
expands Tasks into Vitest tests, runs each Task's Cases concurrently, and stores
|
|
5
|
+
validated JSON Output in Vitest metadata for reporters and later analysis.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add -D @akshatmittal/invoker vitest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Invoker supports Node 24 and Vitest 4.1.10 or newer within Vitest 4.
|
|
14
|
+
|
|
15
|
+
## Define a Workflow
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// regressions/model/tasks/evaluate-models.ts
|
|
19
|
+
import { defineTask } from "@akshatmittal/invoker";
|
|
20
|
+
|
|
21
|
+
export const evaluateModels = defineTask({
|
|
22
|
+
name: "evaluate-models",
|
|
23
|
+
matrix: {
|
|
24
|
+
model: ["gpt-5", "gpt-5-mini"],
|
|
25
|
+
dataset: ["support", "sales"],
|
|
26
|
+
},
|
|
27
|
+
setup: async ({ cases }) => loadFixtures(cases),
|
|
28
|
+
run: async ({ matrix, setup, vitest }) => {
|
|
29
|
+
vitest.expect(setup.has(matrix.dataset)).toBe(true);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
model: matrix.model,
|
|
33
|
+
dataset: matrix.dataset,
|
|
34
|
+
score: await evaluate(matrix, setup),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
teardown: async ({ setup }) => {
|
|
38
|
+
await setup.close();
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// regressions/model/index.test.ts
|
|
45
|
+
import { defineWorkflow } from "@akshatmittal/invoker";
|
|
46
|
+
import { evaluateModels } from "./tasks/evaluate-models.js";
|
|
47
|
+
|
|
48
|
+
defineWorkflow({
|
|
49
|
+
name: "model-regressions",
|
|
50
|
+
metadata: {
|
|
51
|
+
commit: process.env.GITHUB_SHA ?? "local",
|
|
52
|
+
baseline: "2026-08-01",
|
|
53
|
+
},
|
|
54
|
+
tasks: [evaluateModels],
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Matrix literals determine the exact `matrix` type, `setup` determines the exact
|
|
59
|
+
shared setup type, and the exact JSON return type is retained on the Task.
|
|
60
|
+
Omitting `matrix` creates one Case with `{}`. Setup runs once per Task, Cases
|
|
61
|
+
within that Task run concurrently, and teardown runs once after successful
|
|
62
|
+
setup. Tasks run sequentially in their Workflow.
|
|
63
|
+
|
|
64
|
+
## Configure Vitest
|
|
65
|
+
|
|
66
|
+
Invoker uses Vitest's built-in reporters. The JSON reporter includes each
|
|
67
|
+
Case's data at `assertionResults[].meta.invoker`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// vitest.config.ts
|
|
71
|
+
import { defineConfig } from "vitest/config";
|
|
72
|
+
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
test: {
|
|
75
|
+
maxConcurrency: 5,
|
|
76
|
+
reporters: ["default", "json", ...(process.env.GITHUB_ACTIONS === "true" ? ["github-actions" as const] : [])],
|
|
77
|
+
outputFile: {
|
|
78
|
+
json: "./artifacts/invoker-results.json",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run every Workflow or filter to one Task with ordinary Vitest commands:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pnpm vitest run
|
|
88
|
+
pnpm vitest run regressions/model/index.test.ts -t evaluate-models
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The metadata envelope is stable and JSON-compatible:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"schema": 1,
|
|
96
|
+
"matrix": { "model": "gpt-5", "dataset": "support" },
|
|
97
|
+
"metadata": { "commit": "abc123" },
|
|
98
|
+
"output": { "model": "gpt-5", "dataset": "support", "score": 0.92 }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`output` is present only after a successful, JSON-valid Task return. Vitest's
|
|
103
|
+
report remains authoritative for status, failures, timing, hierarchy, and
|
|
104
|
+
retries.
|
|
105
|
+
|
|
106
|
+
## GitHub Actions artifacts
|
|
107
|
+
|
|
108
|
+
Create the output directory before Vitest and upload the report even when the
|
|
109
|
+
run fails:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- run: mkdir -p artifacts && pnpm vitest run
|
|
113
|
+
- if: always()
|
|
114
|
+
uses: actions/upload-artifact@v4
|
|
115
|
+
with:
|
|
116
|
+
name: invoker-results
|
|
117
|
+
path: artifacts/invoker-results.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The artifact provides per-run retention and can be downloaded later for custom
|
|
121
|
+
queries or reports. Invoker does not upload, index, or persist results itself.
|
|
122
|
+
|
|
123
|
+
## v1 scope
|
|
124
|
+
|
|
125
|
+
Invoker does not provide a CLI, directory discovery, custom runner, custom
|
|
126
|
+
reporter, configuration helper, Task-level parallelism, matrix include/exclude,
|
|
127
|
+
or hosted result storage. Use Vitest configuration and your CI runner for those
|
|
128
|
+
concerns.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TestContext } from "vitest";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
4
|
+
type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
5
|
+
readonly [key: string]: JsonValue;
|
|
6
|
+
};
|
|
7
|
+
type JsonObject = {
|
|
8
|
+
readonly [key: string]: JsonValue;
|
|
9
|
+
};
|
|
10
|
+
type Matrix = {
|
|
11
|
+
readonly [axis: string]: readonly JsonValue[];
|
|
12
|
+
};
|
|
13
|
+
type CaseCoordinates<M extends Matrix> = { readonly [K in keyof M]: M[K][number]; };
|
|
14
|
+
interface InvokerMeta<Coordinates extends JsonObject, Output extends JsonValue, Metadata extends JsonObject = JsonObject> {
|
|
15
|
+
schema: 1;
|
|
16
|
+
matrix: Coordinates;
|
|
17
|
+
metadata?: Metadata;
|
|
18
|
+
output?: Output;
|
|
19
|
+
}
|
|
20
|
+
interface TaskContext<Coordinates, Setup> {
|
|
21
|
+
readonly matrix: Coordinates;
|
|
22
|
+
readonly setup: Setup;
|
|
23
|
+
readonly vitest: TestContext;
|
|
24
|
+
}
|
|
25
|
+
type Awaitable<Value> = Value | PromiseLike<Value>;
|
|
26
|
+
type SetupContext<M extends Matrix> = {
|
|
27
|
+
readonly cases: readonly CaseCoordinates<M>[];
|
|
28
|
+
};
|
|
29
|
+
type TeardownContext<M extends Matrix, Setup> = SetupContext<M> & {
|
|
30
|
+
readonly setup: Setup;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/task.d.ts
|
|
34
|
+
declare const taskDefinitionBrand: unique symbol;
|
|
35
|
+
interface TaskDefinition<Name extends string = string, M extends Matrix = Matrix, Setup = unknown, Output extends JsonValue = JsonValue> {
|
|
36
|
+
readonly name: Name;
|
|
37
|
+
readonly matrix: M;
|
|
38
|
+
readonly [taskDefinitionBrand]: {
|
|
39
|
+
readonly setup: Setup;
|
|
40
|
+
readonly output: Output;
|
|
41
|
+
};
|
|
42
|
+
readonly setup?: (context: SetupContext<M>) => Awaitable<Setup>;
|
|
43
|
+
readonly run: (context: TaskContext<CaseCoordinates<M>, Setup>) => Awaitable<Output>;
|
|
44
|
+
readonly teardown?: (context: TeardownContext<M, Setup>) => Awaitable<void>;
|
|
45
|
+
}
|
|
46
|
+
type TaskWithSetup<Name extends string, M extends Matrix, Setup, Output extends JsonValue> = {
|
|
47
|
+
readonly name: Name;
|
|
48
|
+
readonly matrix?: M;
|
|
49
|
+
readonly setup: (context: SetupContext<M>) => Awaitable<Setup>;
|
|
50
|
+
readonly run: (context: TaskContext<CaseCoordinates<M>, Setup>) => Awaitable<Output>;
|
|
51
|
+
readonly teardown?: (context: TeardownContext<M, Setup>) => Awaitable<void>;
|
|
52
|
+
};
|
|
53
|
+
type TaskWithoutSetup<Name extends string, M extends Matrix, Output extends JsonValue> = {
|
|
54
|
+
readonly name: Name;
|
|
55
|
+
readonly matrix?: M;
|
|
56
|
+
readonly setup?: never;
|
|
57
|
+
readonly run: (context: TaskContext<CaseCoordinates<M>, undefined>) => Awaitable<Output>;
|
|
58
|
+
readonly teardown?: never;
|
|
59
|
+
};
|
|
60
|
+
declare function defineTask<const Name extends string, const M extends Matrix = Record<never, never>, Setup = unknown, const Output extends JsonValue = JsonValue>(definition: TaskWithSetup<Name, M, Setup, Output>): TaskDefinition<Name, M, Setup, Output>;
|
|
61
|
+
declare function defineTask<const Name extends string, const M extends Matrix = Record<never, never>, const Output extends JsonValue = JsonValue>(definition: TaskWithoutSetup<Name, M, Output>): TaskDefinition<Name, M, undefined, Output>;
|
|
62
|
+
type AnyTaskDefinition = {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly matrix: Matrix;
|
|
65
|
+
readonly [taskDefinitionBrand]: {
|
|
66
|
+
readonly setup: unknown;
|
|
67
|
+
readonly output: JsonValue;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/workflow.d.ts
|
|
72
|
+
type WorkflowDefinition<Tasks extends readonly [AnyTaskDefinition, ...AnyTaskDefinition[]], Metadata extends JsonObject> = {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly metadata?: Metadata;
|
|
75
|
+
readonly tasks: Tasks;
|
|
76
|
+
};
|
|
77
|
+
declare function defineWorkflow<const Tasks extends readonly [AnyTaskDefinition, ...AnyTaskDefinition[]], const Metadata extends JsonObject = JsonObject>(definition: WorkflowDefinition<Tasks, Metadata>): void;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { type CaseCoordinates, type InvokerMeta, type JsonObject, type JsonPrimitive, type JsonValue, type Matrix, type TaskContext, type TaskDefinition, defineTask, defineWorkflow };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { beforeAll, describe, test } from "vitest";
|
|
2
|
+
//#region src/task.ts
|
|
3
|
+
const taskDefinitionBrand = Symbol("invoker.task");
|
|
4
|
+
function defineTask(definition) {
|
|
5
|
+
return {
|
|
6
|
+
...definition,
|
|
7
|
+
matrix: definition.matrix ?? {},
|
|
8
|
+
[taskDefinitionBrand]: true
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/json.ts
|
|
13
|
+
function assertJson(value, owner, path, ancestors = /* @__PURE__ */ new Set()) {
|
|
14
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
if (!Number.isFinite(value)) fail(owner, path, "expected a finite number");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value !== "object") fail(owner, path, `expected JSON, received ${typeof value}`);
|
|
20
|
+
if (ancestors.has(value)) fail(owner, path, "cyclic values are not JSON");
|
|
21
|
+
ancestors.add(value);
|
|
22
|
+
if (Array.isArray(value)) for (let index = 0; index < value.length; index += 1) {
|
|
23
|
+
if (!(index in value)) fail(owner, `${path}[${index}]`, "sparse arrays are not JSON");
|
|
24
|
+
assertJson(value[index], owner, `${path}[${index}]`, ancestors);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
assertPlainObject(value, owner, path);
|
|
28
|
+
if (Object.getOwnPropertySymbols(value).length > 0) fail(owner, path, "JSON objects cannot have symbol keys");
|
|
29
|
+
for (const [key, child] of Object.entries(value)) assertJson(child, owner, `${path}.${key}`, ancestors);
|
|
30
|
+
}
|
|
31
|
+
ancestors.delete(value);
|
|
32
|
+
}
|
|
33
|
+
function assertPlainObject(value, owner, path) {
|
|
34
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) fail(owner, path, "expected a plain object");
|
|
35
|
+
const prototype = Object.getPrototypeOf(value);
|
|
36
|
+
if (prototype !== Object.prototype && prototype !== null) fail(owner, path, "expected a plain object");
|
|
37
|
+
}
|
|
38
|
+
function assertName(value, owner, path) {
|
|
39
|
+
if (typeof value !== "string" || value.trim() === "") fail(owner, path, "expected a non-empty string");
|
|
40
|
+
}
|
|
41
|
+
function assertOnlyKeys(value, allowed, owner) {
|
|
42
|
+
for (const key of Object.keys(value)) if (!allowed.includes(key)) fail(owner, `.${key}`, "unknown property");
|
|
43
|
+
}
|
|
44
|
+
function canonicalJson(value) {
|
|
45
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
46
|
+
if (value !== null && typeof value === "object") {
|
|
47
|
+
const object = value;
|
|
48
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(",")}}`;
|
|
49
|
+
}
|
|
50
|
+
return JSON.stringify(value);
|
|
51
|
+
}
|
|
52
|
+
function fail(owner, path, message) {
|
|
53
|
+
throw new TypeError(`${owner}${path}: ${message}`);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/matrix.ts
|
|
57
|
+
function expandMatrix(matrix, owner) {
|
|
58
|
+
if (matrix === void 0) return [{}];
|
|
59
|
+
assertPlainObject(matrix, owner, ".matrix");
|
|
60
|
+
let cases = [{}];
|
|
61
|
+
for (const [axis, values] of Object.entries(matrix)) {
|
|
62
|
+
if (axis.trim() === "") fail(owner, ".matrix", "axis names must not be empty");
|
|
63
|
+
if (!Array.isArray(values)) fail(owner, `.matrix.${axis}`, "expected an array");
|
|
64
|
+
if (values.length === 0) fail(owner, `.matrix.${axis}`, "expected at least one value");
|
|
65
|
+
values.forEach((value, index) => {
|
|
66
|
+
assertJson(value, owner, `.matrix.${axis}[${index}]`);
|
|
67
|
+
});
|
|
68
|
+
cases = cases.flatMap((coordinates) => values.map((value) => ({
|
|
69
|
+
...coordinates,
|
|
70
|
+
[axis]: value
|
|
71
|
+
})));
|
|
72
|
+
}
|
|
73
|
+
const coordinates = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const value of cases) {
|
|
75
|
+
const key = canonicalJson(value);
|
|
76
|
+
if (coordinates.has(key)) fail(owner, ".matrix", `duplicate coordinate ${JSON.stringify(value)}`);
|
|
77
|
+
coordinates.add(key);
|
|
78
|
+
}
|
|
79
|
+
return cases;
|
|
80
|
+
}
|
|
81
|
+
function caseName(matrix, index) {
|
|
82
|
+
const axes = Object.entries(matrix).map(([axis, value]) => `${axis}=${JSON.stringify(value)}`).join(", ");
|
|
83
|
+
return axes === "" ? `[${index + 1}]` : `[${index + 1}] ${axes}`;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/workflow.ts
|
|
87
|
+
function defineWorkflow(definition) {
|
|
88
|
+
const workflow = prepareWorkflow(definition);
|
|
89
|
+
describe(workflow.name, { concurrent: false }, () => {
|
|
90
|
+
for (const prepared of workflow.tasks) describe(prepared.task.name, { concurrent: false }, () => {
|
|
91
|
+
let setup;
|
|
92
|
+
const setupTask = prepared.task.setup;
|
|
93
|
+
if (setupTask) beforeAll(async () => {
|
|
94
|
+
setup = await setupTask({ cases: prepared.cases });
|
|
95
|
+
const teardownTask = prepared.task.teardown;
|
|
96
|
+
if (teardownTask) return () => teardownTask({
|
|
97
|
+
cases: prepared.cases,
|
|
98
|
+
setup
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
for (const [index, matrix] of prepared.cases.entries()) {
|
|
102
|
+
const name = prepared.names[index];
|
|
103
|
+
const invoker = prepared.metadata[index];
|
|
104
|
+
test.concurrent(name, { meta: { invoker } }, async (vitest) => {
|
|
105
|
+
const meta = vitest.task.meta;
|
|
106
|
+
delete meta.invoker.output;
|
|
107
|
+
const output = await prepared.task.run({
|
|
108
|
+
matrix,
|
|
109
|
+
setup,
|
|
110
|
+
vitest
|
|
111
|
+
});
|
|
112
|
+
assertJson(output, `Task ${JSON.stringify(prepared.task.name)}`, ".output");
|
|
113
|
+
meta.invoker.output = output;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function prepareWorkflow(definition) {
|
|
120
|
+
assertPlainObject(definition, "Workflow", "");
|
|
121
|
+
assertOnlyKeys(definition, [
|
|
122
|
+
"name",
|
|
123
|
+
"metadata",
|
|
124
|
+
"tasks"
|
|
125
|
+
], "Workflow");
|
|
126
|
+
assertName(definition.name, "Workflow", ".name");
|
|
127
|
+
const metadata = definition.metadata;
|
|
128
|
+
if (metadata !== void 0) {
|
|
129
|
+
assertJson(metadata, `Workflow ${JSON.stringify(definition.name)}`, ".metadata");
|
|
130
|
+
assertPlainObject(metadata, `Workflow ${JSON.stringify(definition.name)}`, ".metadata");
|
|
131
|
+
}
|
|
132
|
+
if (!Array.isArray(definition.tasks) || definition.tasks.length === 0) fail("Workflow", ".tasks", "expected a non-empty Task tuple");
|
|
133
|
+
const names = /* @__PURE__ */ new Set();
|
|
134
|
+
const tasks = definition.tasks.map((value, index) => {
|
|
135
|
+
const owner = `Workflow ${JSON.stringify(definition.name)} Task ${index + 1}`;
|
|
136
|
+
assertPlainObject(value, owner, "");
|
|
137
|
+
const task = value;
|
|
138
|
+
if (task[taskDefinitionBrand] !== true) fail(owner, "", "expected a Task created by defineTask");
|
|
139
|
+
assertOnlyKeys(task, [
|
|
140
|
+
"name",
|
|
141
|
+
"matrix",
|
|
142
|
+
"setup",
|
|
143
|
+
"run",
|
|
144
|
+
"teardown"
|
|
145
|
+
], owner);
|
|
146
|
+
assertName(task.name, owner, ".name");
|
|
147
|
+
if (names.has(task.name)) fail(owner, ".name", `duplicate Task name ${JSON.stringify(task.name)}`);
|
|
148
|
+
names.add(task.name);
|
|
149
|
+
if (typeof task.run !== "function") fail(owner, ".run", "expected a function");
|
|
150
|
+
if (task.setup !== void 0 && typeof task.setup !== "function") fail(owner, ".setup", "expected a function");
|
|
151
|
+
if (task.teardown !== void 0 && typeof task.teardown !== "function") fail(owner, ".teardown", "expected a function");
|
|
152
|
+
if (task.teardown && !task.setup) fail(owner, ".teardown", "requires setup");
|
|
153
|
+
const cases = expandMatrix(task.matrix, `Task ${JSON.stringify(task.name)}`);
|
|
154
|
+
return {
|
|
155
|
+
task,
|
|
156
|
+
cases,
|
|
157
|
+
names: cases.map(caseName),
|
|
158
|
+
metadata: cases.map((matrix) => metadata === void 0 ? {
|
|
159
|
+
schema: 1,
|
|
160
|
+
matrix
|
|
161
|
+
} : {
|
|
162
|
+
schema: 1,
|
|
163
|
+
matrix,
|
|
164
|
+
metadata
|
|
165
|
+
})
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
name: definition.name,
|
|
170
|
+
tasks
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
export { defineTask, defineWorkflow };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akshatmittal/invoker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed matrix-driven regression workflows for Vitest.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"matrix",
|
|
7
|
+
"regression",
|
|
8
|
+
"vitest",
|
|
9
|
+
"workflow"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/akshatmittal/invoker.git"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/**",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"main": "./dist/index.mjs",
|
|
24
|
+
"module": "./dist/index.mjs",
|
|
25
|
+
"types": "./dist/index.d.mts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"import": "./dist/index.mjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.13.3",
|
|
37
|
+
"tsdown": "^0.22.14",
|
|
38
|
+
"typescript": "^7.0.2",
|
|
39
|
+
"vitest": "^4.1.10",
|
|
40
|
+
"@workspace/tsconfig": "0.0.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"vitest": "^4.1.10"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": "^24.18.1"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"dev": "tsdown --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|