@itmetsam/samurai-framework 0.3.0 → 0.4.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/dist/cli/flow-reporter.js +2 -1
- package/dist/flows/core/check.d.ts +4 -1
- package/dist/flows/core/check.js +17 -14
- package/dist/flows/core/expression/check.d.ts +2 -0
- package/dist/flows/core/expression/check.js +3 -3
- package/dist/flows/core/expression/evaluate.d.ts +1 -1
- package/dist/flows/core/expression/scope.d.ts +3 -2
- package/dist/flows/core/expression/scope.js +15 -6
- package/dist/flows/core/index.d.ts +2 -1
- package/dist/flows/core/index.js +2 -1
- package/dist/flows/core/nodes/define.d.ts +4 -0
- package/dist/flows/core/nodes/index.d.ts +4 -66
- package/dist/flows/core/nodes/index.js +12 -38
- package/dist/flows/core/steps.d.ts +42 -0
- package/dist/flows/core/steps.js +47 -0
- package/dist/flows/run/events.d.ts +3 -1
- package/dist/flows/run/handlers/index.d.ts +1 -3
- package/dist/flows/run/handlers/index.js +3 -8
- package/dist/flows/run/handlers/support.d.ts +4 -1
- package/dist/flows/run/handlers/support.js +1 -1
- package/dist/flows/run/index.d.ts +1 -1
- package/dist/flows/run/index.js +1 -1
- package/dist/flows/run/project.js +2 -0
- package/dist/flows/run/run.d.ts +1 -1
- package/dist/flows/run/run.js +7 -1
- package/dist/flows/steps/api.d.ts +23 -0
- package/dist/flows/steps/api.js +40 -0
- package/dist/flows/steps/catalog.d.ts +16 -0
- package/dist/flows/steps/catalog.js +29 -0
- package/dist/flows/steps/define.d.ts +49 -0
- package/dist/flows/steps/define.js +40 -0
- package/dist/flows/steps/handler.d.ts +4 -0
- package/dist/flows/steps/handler.js +70 -0
- package/dist/flows/steps/index.d.ts +5 -0
- package/dist/flows/steps/index.js +4 -0
- package/dist/flows/{core/nodes → steps}/set-variable.d.ts +4 -1
- package/dist/flows/steps/set-variable.js +20 -0
- package/dist/flows/steps/wait.d.ts +8 -0
- package/dist/flows/steps/wait.js +14 -0
- package/package.json +5 -1
- package/dist/flows/core/nodes/api.d.ts +0 -9
- package/dist/flows/core/nodes/api.js +0 -12
- package/dist/flows/core/nodes/database.d.ts +0 -9
- package/dist/flows/core/nodes/database.js +0 -12
- package/dist/flows/core/nodes/email.d.ts +0 -9
- package/dist/flows/core/nodes/email.js +0 -12
- package/dist/flows/core/nodes/script.d.ts +0 -9
- package/dist/flows/core/nodes/script.js +0 -12
- package/dist/flows/core/nodes/set-variable.js +0 -39
- package/dist/flows/core/nodes/wait.d.ts +0 -15
- package/dist/flows/core/nodes/wait.js +0 -18
- package/dist/flows/run/handlers/set-variable.d.ts +0 -2
- package/dist/flows/run/handlers/set-variable.js +0 -22
- package/dist/flows/run/handlers/wait.d.ts +0 -2
- package/dist/flows/run/handlers/wait.js +0 -10
package/dist/flows/run/run.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { inFillOrder, meetOf, nodeDef, } from "../core/index.js";
|
|
2
|
+
import logger from "../../logger/index.js";
|
|
3
|
+
import { STEP_NODES } from "../steps/catalog.js";
|
|
2
4
|
import { HANDLERS } from "./handlers/index.js";
|
|
3
5
|
const defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
4
6
|
if (signal.aborted)
|
|
@@ -26,7 +28,8 @@ export async function runFlow(options) {
|
|
|
26
28
|
const { flow, onEvent = () => { } } = options;
|
|
27
29
|
const signal = options.signal ?? new AbortController().signal;
|
|
28
30
|
const handlers = options.handlers ?? HANDLERS;
|
|
29
|
-
const
|
|
31
|
+
const nodes = options.nodes ?? STEP_NODES;
|
|
32
|
+
const defOf = (kind) => nodes.find((d) => d.kind === kind) ??
|
|
30
33
|
(() => {
|
|
31
34
|
try {
|
|
32
35
|
return nodeDef(kind);
|
|
@@ -66,6 +69,8 @@ export async function runFlow(options) {
|
|
|
66
69
|
sleep: options.sleep ?? defaultSleep,
|
|
67
70
|
runTests: (tests, onTestEvent) => options.runTests({ tests, onEvent: onTestEvent, signal }),
|
|
68
71
|
onTestEvent: (nodeId, event) => onEvent({ type: "test", nodeId, event }),
|
|
72
|
+
nodes,
|
|
73
|
+
log: (nodeId, level, text) => logger[level](`[${nodeId}] ${text}`),
|
|
69
74
|
};
|
|
70
75
|
let stopped;
|
|
71
76
|
const visited = new Set();
|
|
@@ -73,6 +78,7 @@ export async function runFlow(options) {
|
|
|
73
78
|
results[node.id] = result;
|
|
74
79
|
if (node.key)
|
|
75
80
|
context.nodes[node.key] = {
|
|
81
|
+
...result.outputs,
|
|
76
82
|
status: result.status,
|
|
77
83
|
durationMs: result.durationMs,
|
|
78
84
|
error: result.error,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Any response passes and outputs its status; a network error or a bad URL fails the node */
|
|
2
|
+
export declare const api: import("./define.js").StepDefinition<import("./define.js").RunConfigOf<readonly [{
|
|
3
|
+
readonly path: "config.method";
|
|
4
|
+
readonly type: "select";
|
|
5
|
+
readonly label: "Method";
|
|
6
|
+
readonly options: {
|
|
7
|
+
label: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}[];
|
|
10
|
+
readonly default: "GET";
|
|
11
|
+
}, {
|
|
12
|
+
readonly path: "config.url";
|
|
13
|
+
readonly type: "expression";
|
|
14
|
+
readonly label: "URL";
|
|
15
|
+
readonly placeholder: "env.baseUrl + \"/health\"";
|
|
16
|
+
readonly default: "";
|
|
17
|
+
}, {
|
|
18
|
+
readonly path: "config.body";
|
|
19
|
+
readonly type: "code";
|
|
20
|
+
readonly label: "Body";
|
|
21
|
+
readonly placeholder: "{ \"name\": \"test\" }";
|
|
22
|
+
readonly default: "";
|
|
23
|
+
}]>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineStep } from "./define.js";
|
|
2
|
+
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"].map((m) => ({ label: m, value: m }));
|
|
3
|
+
const isJson = (text) => {
|
|
4
|
+
try {
|
|
5
|
+
JSON.parse(text);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/** Any response passes and outputs its status; a network error or a bad URL fails the node */
|
|
13
|
+
export const api = defineStep({
|
|
14
|
+
kind: "api",
|
|
15
|
+
label: "HTTP request",
|
|
16
|
+
icon: "globe",
|
|
17
|
+
section: "integrations",
|
|
18
|
+
fields: [
|
|
19
|
+
{ path: "config.method", type: "select", label: "Method", options: METHODS, default: "GET" },
|
|
20
|
+
{ path: "config.url", type: "expression", label: "URL", placeholder: 'env.baseUrl + "/health"', default: "" },
|
|
21
|
+
{ path: "config.body", type: "code", label: "Body", placeholder: '{ "name": "test" }', default: "" },
|
|
22
|
+
],
|
|
23
|
+
summary: "{config.method} {config.url}",
|
|
24
|
+
outputs: [
|
|
25
|
+
{ name: "statusCode", label: "Status code" },
|
|
26
|
+
{ name: "body", label: "Response body" },
|
|
27
|
+
],
|
|
28
|
+
async run({ config, signal }) {
|
|
29
|
+
const body = config.method !== "GET" && config.body ? config.body : undefined;
|
|
30
|
+
const res = await fetch(String(config.url), {
|
|
31
|
+
method: config.method,
|
|
32
|
+
signal,
|
|
33
|
+
...(body !== undefined && {
|
|
34
|
+
body,
|
|
35
|
+
headers: { "content-type": isJson(body) ? "application/json" : "text/plain" },
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
return { outputs: { statusCode: res.status, body: await res.text() } };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnyNodeDefinition, FlowFile, FlowProblem, StepInfo } from "../core/index.js";
|
|
2
|
+
import type { Handler } from "../run/handlers/support.js";
|
|
3
|
+
import { type StepDefinition } from "./define.js";
|
|
4
|
+
/** The built-in steps, in library order */
|
|
5
|
+
export declare const STEPS: readonly StepDefinition<any>[];
|
|
6
|
+
export declare const STEP_NODES: readonly AnyNodeDefinition[];
|
|
7
|
+
export declare const STEP_HANDLERS: Readonly<Record<string, Handler>>;
|
|
8
|
+
/** The steps as data, for the app */
|
|
9
|
+
export declare const stepCatalog: (steps?: readonly StepDefinition<any>[]) => StepInfo[];
|
|
10
|
+
/** The project's steps; the built-in ones for now (test-project steps come later) */
|
|
11
|
+
export declare function listSteps(): Promise<StepInfo[]>;
|
|
12
|
+
/** Only the steps' own `validate` problems: the app checks everything else itself */
|
|
13
|
+
export declare function validateSteps(flow: FlowFile, options: {
|
|
14
|
+
tests?: string[];
|
|
15
|
+
groups?: string[];
|
|
16
|
+
}): FlowProblem[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { api } from "./api.js";
|
|
2
|
+
import { infoOf, stepNode } from "./define.js";
|
|
3
|
+
import { stepHandler } from "./handler.js";
|
|
4
|
+
import { setVariable } from "./set-variable.js";
|
|
5
|
+
import { wait } from "./wait.js";
|
|
6
|
+
/** The built-in steps, in library order */
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
export const STEPS = [wait, setVariable, api];
|
|
9
|
+
export const STEP_NODES = STEPS.map(stepNode);
|
|
10
|
+
export const STEP_HANDLERS = Object.fromEntries(STEPS.map((s) => [s.kind, stepHandler(s)]));
|
|
11
|
+
/** The steps as data, for the app */
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export const stepCatalog = (steps = STEPS) => steps.map(infoOf);
|
|
14
|
+
/** The project's steps; the built-in ones for now (test-project steps come later) */
|
|
15
|
+
export async function listSteps() {
|
|
16
|
+
return stepCatalog();
|
|
17
|
+
}
|
|
18
|
+
/** Only the steps' own `validate` problems: the app checks everything else itself */
|
|
19
|
+
export function validateSteps(flow, options) {
|
|
20
|
+
const tests = options.tests ? new Set(options.tests) : undefined;
|
|
21
|
+
const groups = options.groups ? new Set(options.groups) : undefined;
|
|
22
|
+
return flow.nodes.flatMap((n) => {
|
|
23
|
+
const step = STEPS.find((s) => s.kind === n.kind);
|
|
24
|
+
if (!step?.validate)
|
|
25
|
+
return [];
|
|
26
|
+
const def = STEP_NODES.find((d) => d.kind === n.kind);
|
|
27
|
+
return step.validate({ ...n, config: { ...Object.fromEntries(step.fields.map((f) => [f.path.slice(7), "default" in f ? f.default : undefined])), ...n.config } }, { label: def.label, tests, groups });
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type AnyNodeDefinition, type NodeCoreDefinition, type StepField, type StepInfo } from "../core/index.js";
|
|
2
|
+
type RunValueOf<F> = F extends {
|
|
3
|
+
type: "expression";
|
|
4
|
+
} ? unknown : F extends {
|
|
5
|
+
type: "number";
|
|
6
|
+
} ? number : F extends {
|
|
7
|
+
type: "switch";
|
|
8
|
+
} ? boolean : string;
|
|
9
|
+
/** `config` as `run` sees it: expression fields arrive evaluated */
|
|
10
|
+
export type RunConfigOf<F extends readonly StepField[]> = {
|
|
11
|
+
[K in F[number] as K["path"] extends `config.${infer P}` ? P : never]: RunValueOf<K>;
|
|
12
|
+
};
|
|
13
|
+
export interface StepRunInput<C> {
|
|
14
|
+
config: C;
|
|
15
|
+
env: Readonly<Record<string, unknown>>;
|
|
16
|
+
vars: Readonly<Record<string, unknown>>;
|
|
17
|
+
node: {
|
|
18
|
+
id: string;
|
|
19
|
+
key?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
};
|
|
22
|
+
/** Aborted when the run is stopped */
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
/** Waits, unless the run is stopped first */
|
|
25
|
+
sleep: (ms: number) => Promise<void>;
|
|
26
|
+
log: (level: "info" | "warn" | "error", text: string) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface StepResult {
|
|
29
|
+
/** Declared in `outputs`; readable later as `nodes.<key>.<name>` */
|
|
30
|
+
outputs?: Record<string, unknown>;
|
|
31
|
+
/** Only the name the `writesVar` field holds */
|
|
32
|
+
vars?: Record<string, unknown>;
|
|
33
|
+
note?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface StepDefinition<C = Record<string, unknown>> extends StepInfo {
|
|
36
|
+
/** Problems with this node alone; runs in the engine (checks and before a run) */
|
|
37
|
+
validate?: NodeCoreDefinition<string, any>["validate"];
|
|
38
|
+
/** What the step does in a run: return to pass, throw to fail */
|
|
39
|
+
run: (input: StepRunInput<C>) => Promise<StepResult | void>;
|
|
40
|
+
}
|
|
41
|
+
/** Declares a flow step: one way in, one way out. Throws for a definition that can't work. */
|
|
42
|
+
export declare function defineStep<const F extends readonly StepField[]>(def: Omit<StepDefinition<RunConfigOf<F>>, "fields"> & {
|
|
43
|
+
fields: F;
|
|
44
|
+
}): StepDefinition<RunConfigOf<F>>;
|
|
45
|
+
/** The step as data, without its functions: what the catalog holds */
|
|
46
|
+
export declare function infoOf(step: StepDefinition<never>): StepInfo;
|
|
47
|
+
/** The definition the walker and checkFlow use for a step */
|
|
48
|
+
export declare const stepNode: (step: StepDefinition<any>) => AnyNodeDefinition;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CLOSED_KINDS, RESERVED_OUTPUTS, STEP_KIND, stepNodeOf, } from "../core/index.js";
|
|
2
|
+
/** Declares a flow step: one way in, one way out. Throws for a definition that can't work. */
|
|
3
|
+
export function defineStep(def) {
|
|
4
|
+
const name = `Step "${def.kind}"`;
|
|
5
|
+
if (CLOSED_KINDS.includes(def.kind))
|
|
6
|
+
throw new Error(`${name}: "${def.kind}" is a closed kind; steps can't replace it.`);
|
|
7
|
+
if (!STEP_KIND.test(def.kind))
|
|
8
|
+
throw new Error(`${name}: "${def.kind}" isn't a valid step kind. Use lowercase letters, digits and -.`);
|
|
9
|
+
for (const f of def.fields) {
|
|
10
|
+
if (!f.path.startsWith("config."))
|
|
11
|
+
throw new Error(`${name}: field "${f.path}" must be a config. path.`);
|
|
12
|
+
if (f.pattern !== undefined)
|
|
13
|
+
try {
|
|
14
|
+
new RegExp(f.pattern);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error(`${name}: field "${f.path}" has a pattern that isn't a valid regular expression.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const outputs = (def.outputs ?? []).map((o) => o.name);
|
|
21
|
+
for (const o of outputs) {
|
|
22
|
+
if (RESERVED_OUTPUTS.includes(o))
|
|
23
|
+
throw new Error(`${name}: output "${o}" is reserved.`);
|
|
24
|
+
if (outputs.indexOf(o) !== outputs.lastIndexOf(o))
|
|
25
|
+
throw new Error(`${name}: output "${o}" is declared twice.`);
|
|
26
|
+
}
|
|
27
|
+
if (def.writesVar !== undefined && !def.fields.some((f) => f.path === def.writesVar))
|
|
28
|
+
throw new Error(`${name}: writesVar "${def.writesVar}" isn't one of its fields.`);
|
|
29
|
+
return def;
|
|
30
|
+
}
|
|
31
|
+
/** The step as data, without its functions: what the catalog holds */
|
|
32
|
+
export function infoOf(step) {
|
|
33
|
+
const { run: _run, validate: _validate, ...info } = step;
|
|
34
|
+
void _run;
|
|
35
|
+
void _validate;
|
|
36
|
+
return JSON.parse(JSON.stringify(info));
|
|
37
|
+
}
|
|
38
|
+
/** The definition the walker and checkFlow use for a step */
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
export const stepNode = (step) => stepNodeOf(infoOf(step), step.validate);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { evaluateText, getPath, withDefaults } from "../core/index.js";
|
|
2
|
+
import { fail, problemAt } from "../run/handlers/support.js";
|
|
3
|
+
import { stepNode } from "./define.js";
|
|
4
|
+
const keysOf = (o) => Object.keys(o ?? {});
|
|
5
|
+
/** The one handler every step runs through */
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
export function stepHandler(step) {
|
|
8
|
+
const def = stepNode(step);
|
|
9
|
+
return async (node, run) => {
|
|
10
|
+
const typed = withDefaults(def, node);
|
|
11
|
+
const config = { ...typed.config };
|
|
12
|
+
for (const f of step.fields) {
|
|
13
|
+
if (f.type !== "expression")
|
|
14
|
+
continue;
|
|
15
|
+
const text = String(getPath(typed, f.path) ?? "");
|
|
16
|
+
const problem = problemAt(node, run, text);
|
|
17
|
+
if (problem)
|
|
18
|
+
return { status: "failed", error: problem };
|
|
19
|
+
try {
|
|
20
|
+
config[f.path.slice("config.".length)] = evaluateText(text, run.context);
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return fail(e);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// A step that ignores the signal must not hold the run: Stop wins the race
|
|
27
|
+
const stopped = new Promise((resolve) => {
|
|
28
|
+
if (run.signal.aborted)
|
|
29
|
+
resolve("stopped");
|
|
30
|
+
run.signal.addEventListener("abort", () => resolve("stopped"), { once: true });
|
|
31
|
+
});
|
|
32
|
+
let result;
|
|
33
|
+
try {
|
|
34
|
+
result = await Promise.race([
|
|
35
|
+
step.run({
|
|
36
|
+
config,
|
|
37
|
+
env: { ...run.context.env },
|
|
38
|
+
vars: { ...run.context.vars },
|
|
39
|
+
node: { id: node.id, ...(node.key && { key: node.key }), ...(node.title && { title: node.title }) },
|
|
40
|
+
signal: run.signal,
|
|
41
|
+
sleep: (ms) => run.sleep(ms, run.signal),
|
|
42
|
+
log: (level, text) => run.log(node.id, level, text),
|
|
43
|
+
}),
|
|
44
|
+
stopped,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
if (run.signal.aborted)
|
|
49
|
+
return "cancelled";
|
|
50
|
+
return fail(e);
|
|
51
|
+
}
|
|
52
|
+
if (result === "stopped" || run.signal.aborted)
|
|
53
|
+
return "cancelled";
|
|
54
|
+
const declared = new Set((step.outputs ?? []).map((o) => o.name));
|
|
55
|
+
const extra = keysOf(result?.outputs).filter((k) => !declared.has(k));
|
|
56
|
+
if (extra.length)
|
|
57
|
+
return { status: "failed", error: `${step.label} returned outputs it doesn't declare: ${extra.join(", ")}.` };
|
|
58
|
+
const allowed = step.writesVar ? getPath(typed, step.writesVar) : undefined;
|
|
59
|
+
const wrongVars = keysOf(result?.vars).filter((k) => k !== allowed);
|
|
60
|
+
if (wrongVars.length)
|
|
61
|
+
return { status: "failed", error: `${step.label} wrote variables it doesn't declare: ${wrongVars.join(", ")}.` };
|
|
62
|
+
Object.assign(run.context.vars, result?.vars);
|
|
63
|
+
return {
|
|
64
|
+
status: "passed",
|
|
65
|
+
...(keysOf(result?.outputs).length > 0 && { outputs: result.outputs }),
|
|
66
|
+
...(keysOf(result?.vars).length > 0 && { wrote: result.vars }),
|
|
67
|
+
...(result?.note && { note: result.note }),
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Flow steps: define your own, and the built-in ones. Node only. */
|
|
2
|
+
export { defineStep, stepNode, type RunConfigOf, type StepDefinition, type StepResult, type StepRunInput, } from "./define.js";
|
|
3
|
+
export { stepHandler } from "./handler.js";
|
|
4
|
+
export { STEPS, STEP_HANDLERS, STEP_NODES, listSteps, stepCatalog, validateSteps, } from "./catalog.js";
|
|
5
|
+
export type { StepField, StepInfo, StepOutput, StepSection } from "../core/index.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Flow steps: define your own, and the built-in ones. Node only. */
|
|
2
|
+
export { defineStep, stepNode, } from "./define.js";
|
|
3
|
+
export { stepHandler } from "./handler.js";
|
|
4
|
+
export { STEPS, STEP_HANDLERS, STEP_NODES, listSteps, stepCatalog, validateSteps, } from "./catalog.js";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
export declare const setVariable: import("./define.js").
|
|
1
|
+
export declare const setVariable: import("./define.js").StepDefinition<import("./define.js").RunConfigOf<readonly [{
|
|
2
2
|
readonly path: "config.name";
|
|
3
3
|
readonly type: "text";
|
|
4
4
|
readonly label: "Name";
|
|
5
5
|
readonly placeholder: "orderId";
|
|
6
6
|
readonly default: "";
|
|
7
|
+
readonly required: true;
|
|
8
|
+
readonly pattern: "^[a-z][a-z0-9_]*$";
|
|
9
|
+
readonly patternMessage: "use lowercase letters, digits and _ for the name.";
|
|
7
10
|
}, {
|
|
8
11
|
readonly path: "config.value";
|
|
9
12
|
readonly type: "expression";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineStep } from "./define.js";
|
|
2
|
+
export const setVariable = defineStep({
|
|
3
|
+
kind: "set-variable",
|
|
4
|
+
label: "Set variable",
|
|
5
|
+
icon: "braces",
|
|
6
|
+
section: "control",
|
|
7
|
+
fields: [
|
|
8
|
+
{
|
|
9
|
+
path: "config.name", type: "text", label: "Name", placeholder: "orderId", default: "",
|
|
10
|
+
required: true, pattern: "^[a-z][a-z0-9_]*$",
|
|
11
|
+
patternMessage: "use lowercase letters, digits and _ for the name.",
|
|
12
|
+
},
|
|
13
|
+
{ path: "config.value", type: "expression", label: "Value", placeholder: '"order-" + run.environment', default: "" },
|
|
14
|
+
],
|
|
15
|
+
summary: "vars.{config.name} = {config.value}",
|
|
16
|
+
writesVar: "config.name",
|
|
17
|
+
async run({ config }) {
|
|
18
|
+
return { vars: { [config.name]: config.value } };
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const wait: import("./define.js").StepDefinition<import("./define.js").RunConfigOf<readonly [{
|
|
2
|
+
readonly path: "config.ms";
|
|
3
|
+
readonly type: "number";
|
|
4
|
+
readonly label: "Duration";
|
|
5
|
+
readonly unit: "ms";
|
|
6
|
+
readonly default: 1000;
|
|
7
|
+
readonly min: 0;
|
|
8
|
+
}]>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineStep } from "./define.js";
|
|
2
|
+
export const wait = defineStep({
|
|
3
|
+
kind: "wait",
|
|
4
|
+
label: "Wait",
|
|
5
|
+
icon: "timer",
|
|
6
|
+
section: "control",
|
|
7
|
+
fields: [
|
|
8
|
+
{ path: "config.ms", type: "number", label: "Duration", unit: "ms", default: 1000, min: 0 },
|
|
9
|
+
],
|
|
10
|
+
summary: "{config.ms} ms",
|
|
11
|
+
async run({ config, sleep }) {
|
|
12
|
+
await sleep(config.ms);
|
|
13
|
+
},
|
|
14
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itmetsam/samurai-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Browser test framework over WebDriver BiDi: auto-waiting locators, environments and secrets, a recorder, and a command line.",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"exports": {
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"types": "./dist/flows/run/index.d.ts",
|
|
37
37
|
"default": "./dist/flows/run/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./flows/steps": {
|
|
40
|
+
"types": "./dist/flows/steps/index.d.ts",
|
|
41
|
+
"default": "./dist/flows/steps/index.js"
|
|
42
|
+
},
|
|
39
43
|
"./package.json": "./package.json"
|
|
40
44
|
},
|
|
41
45
|
"scripts": {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const api: import("./define.js").NodeCoreDefinition<"api", import("./define.js").ConfigOf<readonly [{
|
|
2
|
-
readonly path: "title";
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly label: "Title";
|
|
5
|
-
}, {
|
|
6
|
-
readonly path: "subtitle";
|
|
7
|
-
readonly type: "text";
|
|
8
|
-
readonly label: "Description";
|
|
9
|
-
}]>>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
// Declared so flows can hold it; no handler runs it yet (unsupported).
|
|
3
|
-
export const api = defineNode({
|
|
4
|
-
kind: "api",
|
|
5
|
-
label: "HTTP request",
|
|
6
|
-
shape: "step",
|
|
7
|
-
fields: [
|
|
8
|
-
{ path: "title", type: "text", label: "Title" },
|
|
9
|
-
{ path: "subtitle", type: "text", label: "Description" },
|
|
10
|
-
],
|
|
11
|
-
outputs: true,
|
|
12
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const database: import("./define.js").NodeCoreDefinition<"database", import("./define.js").ConfigOf<readonly [{
|
|
2
|
-
readonly path: "title";
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly label: "Title";
|
|
5
|
-
}, {
|
|
6
|
-
readonly path: "subtitle";
|
|
7
|
-
readonly type: "text";
|
|
8
|
-
readonly label: "Description";
|
|
9
|
-
}]>>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
// Declared so flows can hold it; no handler runs it yet (unsupported).
|
|
3
|
-
export const database = defineNode({
|
|
4
|
-
kind: "database",
|
|
5
|
-
label: "Database query",
|
|
6
|
-
shape: "step",
|
|
7
|
-
fields: [
|
|
8
|
-
{ path: "title", type: "text", label: "Title" },
|
|
9
|
-
{ path: "subtitle", type: "text", label: "Description" },
|
|
10
|
-
],
|
|
11
|
-
outputs: true,
|
|
12
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const email: import("./define.js").NodeCoreDefinition<"email", import("./define.js").ConfigOf<readonly [{
|
|
2
|
-
readonly path: "title";
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly label: "Title";
|
|
5
|
-
}, {
|
|
6
|
-
readonly path: "subtitle";
|
|
7
|
-
readonly type: "text";
|
|
8
|
-
readonly label: "Description";
|
|
9
|
-
}]>>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
// Declared so flows can hold it; no handler runs it yet (unsupported).
|
|
3
|
-
export const email = defineNode({
|
|
4
|
-
kind: "email",
|
|
5
|
-
label: "Email inbox",
|
|
6
|
-
shape: "step",
|
|
7
|
-
fields: [
|
|
8
|
-
{ path: "title", type: "text", label: "Title" },
|
|
9
|
-
{ path: "subtitle", type: "text", label: "Description" },
|
|
10
|
-
],
|
|
11
|
-
outputs: true,
|
|
12
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const script: import("./define.js").NodeCoreDefinition<"script", import("./define.js").ConfigOf<readonly [{
|
|
2
|
-
readonly path: "title";
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly label: "Title";
|
|
5
|
-
}, {
|
|
6
|
-
readonly path: "subtitle";
|
|
7
|
-
readonly type: "text";
|
|
8
|
-
readonly label: "Description";
|
|
9
|
-
}]>>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
// Declared so flows can hold it; no handler runs it yet (unsupported).
|
|
3
|
-
export const script = defineNode({
|
|
4
|
-
kind: "script",
|
|
5
|
-
label: "Script",
|
|
6
|
-
shape: "step",
|
|
7
|
-
fields: [
|
|
8
|
-
{ path: "title", type: "text", label: "Title" },
|
|
9
|
-
{ path: "subtitle", type: "text", label: "Description" },
|
|
10
|
-
],
|
|
11
|
-
outputs: true,
|
|
12
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
export const setVariable = defineNode({
|
|
3
|
-
kind: "set-variable",
|
|
4
|
-
label: "Set variable",
|
|
5
|
-
shape: "step",
|
|
6
|
-
fields: [
|
|
7
|
-
{
|
|
8
|
-
path: "config.name",
|
|
9
|
-
type: "text",
|
|
10
|
-
label: "Name",
|
|
11
|
-
placeholder: "orderId",
|
|
12
|
-
default: "",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
path: "config.value",
|
|
16
|
-
type: "expression",
|
|
17
|
-
label: "Value",
|
|
18
|
-
placeholder: '"order-" + run.environment',
|
|
19
|
-
default: "",
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
validate: (node, ctx) => node.config.name === ""
|
|
23
|
-
? [
|
|
24
|
-
{
|
|
25
|
-
level: "error",
|
|
26
|
-
nodeId: node.id,
|
|
27
|
-
message: `${ctx.label}: give the variable a name.`,
|
|
28
|
-
},
|
|
29
|
-
]
|
|
30
|
-
: /^[a-z][a-z0-9_]*$/.test(node.config.name)
|
|
31
|
-
? []
|
|
32
|
-
: [
|
|
33
|
-
{
|
|
34
|
-
level: "error",
|
|
35
|
-
nodeId: node.id,
|
|
36
|
-
message: `${ctx.label}: use lowercase letters, digits and _ for the name.`,
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export declare const wait: import("./define.js").NodeCoreDefinition<"wait", import("./define.js").ConfigOf<readonly [{
|
|
2
|
-
readonly path: "title";
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly label: "Title";
|
|
5
|
-
}, {
|
|
6
|
-
readonly path: "subtitle";
|
|
7
|
-
readonly type: "text";
|
|
8
|
-
readonly label: "Description";
|
|
9
|
-
}, {
|
|
10
|
-
readonly path: "config.ms";
|
|
11
|
-
readonly type: "number";
|
|
12
|
-
readonly label: "Duration";
|
|
13
|
-
readonly unit: "ms";
|
|
14
|
-
readonly default: 1000;
|
|
15
|
-
}]>>;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { defineNode } from "./define.js";
|
|
2
|
-
export const wait = defineNode({
|
|
3
|
-
kind: "wait",
|
|
4
|
-
label: "Wait",
|
|
5
|
-
shape: "step",
|
|
6
|
-
fields: [
|
|
7
|
-
{ path: "title", type: "text", label: "Title" },
|
|
8
|
-
{ path: "subtitle", type: "text", label: "Description" },
|
|
9
|
-
{
|
|
10
|
-
path: "config.ms",
|
|
11
|
-
type: "number",
|
|
12
|
-
label: "Duration",
|
|
13
|
-
unit: "ms",
|
|
14
|
-
default: 1000,
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
outputs: true,
|
|
18
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { evaluateText, KEY_PATTERN } from "../../core/index.js";
|
|
2
|
-
import { fail, problemAt } from "./support.js";
|
|
3
|
-
export const setVariable = async (node, run) => {
|
|
4
|
-
const name = node.config?.name;
|
|
5
|
-
if (typeof name !== "string" || !KEY_PATTERN.test(name))
|
|
6
|
-
return {
|
|
7
|
-
status: "failed",
|
|
8
|
-
error: "Give the variable a name: lowercase letters, digits and _.",
|
|
9
|
-
};
|
|
10
|
-
const text = String(node.config?.value ?? "");
|
|
11
|
-
const problem = problemAt(node, run, text);
|
|
12
|
-
if (problem)
|
|
13
|
-
return { status: "failed", error: problem };
|
|
14
|
-
try {
|
|
15
|
-
const value = evaluateText(text, run.context);
|
|
16
|
-
run.context.vars[name] = value;
|
|
17
|
-
return { status: "passed", wrote: { [name]: value } };
|
|
18
|
-
}
|
|
19
|
-
catch (e) {
|
|
20
|
-
return fail(e);
|
|
21
|
-
}
|
|
22
|
-
};
|