@visualautomate/plugin-sdk 0.6.1 → 0.8.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 +123 -92
- package/dist/index.cjs +659 -203
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +649 -203
- package/dist/index.mjs.map +3 -3
- package/dist/manifest-check.d.ts +26 -0
- package/dist/platform-rules.d.ts +52 -5
- package/dist/runtime.d.ts +42 -0
- package/dist/simulate.d.ts +4 -0
- package/dist/types.d.ts +84 -5
- package/dist/validate.d.ts +0 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,91 +1,79 @@
|
|
|
1
|
-
# @visualautomate/plugin-sdk
|
|
2
|
-
|
|
3
|
-
The types a VisualAutomate plugin is written against, and a way to run one on
|
|
4
|
-
your own machine.
|
|
5
|
-
|
|
6
|
-
Most people do not need to install this. `@visualautomate/cli` carries it and
|
|
7
|
-
writes the types into your project, so a scaffolded plugin has no dependencies
|
|
8
|
-
at all. Install it directly if you want the simulator in your own test suite.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install --save-dev @visualautomate/plugin-sdk
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## A plugin
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import type { PluginModule } from "@visualautomate/plugin-sdk"
|
|
18
|
-
|
|
19
|
-
const plugin: PluginModule = {
|
|
20
|
-
async execute(input, config, context) {
|
|
21
|
-
const bytes = await context.storage.get(config.path as string)
|
|
22
|
-
if (!bytes) return { output: "error", data: { error: "No such file." } }
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
output: "success",
|
|
26
|
-
data: { size: bytes.length },
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default plugin
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`output` names which port the run leaves by, and has to be one of the `outputs`
|
|
35
|
-
in your manifest. `data` is what the next steps read.
|
|
36
|
-
|
|
37
|
-
## What `context` has
|
|
38
|
-
|
|
39
|
-
| | |
|
|
40
|
-
|---|---|
|
|
41
|
-
| `storage` | the account's files — `put`, `get`, `delete`, `list`, `head`, `signedUrl`, `download` |
|
|
42
|
-
| `state` | values that outlive one run, scoped to the workflow — `get`, `set
|
|
43
|
-
| `media` | ffmpeg, by operation name. Absent when the server has no sandbox configured |
|
|
44
|
-
| `
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
import
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
result.
|
|
65
|
-
result.
|
|
66
|
-
result.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
code around
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
await simulate(plugin, {
|
|
79
|
-
connections: {
|
|
80
|
-
async get() {
|
|
81
|
-
return { id: "test", async request() { return { ok: true, status: 200, body: {} } } }
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Asserting on what your plugin *asked* the provider for is a better test than any
|
|
88
|
-
imitation of the provider would be.
|
|
1
|
+
# @visualautomate/plugin-sdk
|
|
2
|
+
|
|
3
|
+
The types a VisualAutomate plugin is written against, and a way to run one on
|
|
4
|
+
your own machine.
|
|
5
|
+
|
|
6
|
+
Most people do not need to install this. `@visualautomate/cli` carries it and
|
|
7
|
+
writes the types into your project, so a scaffolded plugin has no dependencies
|
|
8
|
+
at all. Install it directly if you want the simulator in your own test suite.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install --save-dev @visualautomate/plugin-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## A plugin
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { PluginModule } from "@visualautomate/plugin-sdk"
|
|
18
|
+
|
|
19
|
+
const plugin: PluginModule = {
|
|
20
|
+
async execute(input, config, context) {
|
|
21
|
+
const bytes = await context.storage.get(config.path as string)
|
|
22
|
+
if (!bytes) return { output: "error", data: { error: "No such file." } }
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
output: "success",
|
|
26
|
+
data: { size: bytes.length },
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`output` names which port the run leaves by, and has to be one of the `outputs`
|
|
35
|
+
in your manifest. `data` is what the next steps read.
|
|
36
|
+
|
|
37
|
+
## What `context` has
|
|
38
|
+
|
|
39
|
+
| | |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `storage` | the account's files — `put`, `get`, `delete`, `list`, `head`, `signedUrl`, `shareUrl`, `download` |
|
|
42
|
+
| `state` | values that outlive one run, scoped to the workflow — `get`, `set` (at most 128 KB, `ttlSeconds` at least 60), `delete`, `list` (`{ keys: [{ key, expiresAt }], complete }`) |
|
|
43
|
+
| `media` | ffmpeg, by operation name. Absent when the server has no sandbox configured |
|
|
44
|
+
| `workflowId`, `nodeId`, `userId`, `executionId`, `startedAt`, `secrets` | about the run |
|
|
45
|
+
|
|
46
|
+
There is no connected account for your steps: only VisualAutomate's own apps act
|
|
47
|
+
as one (`checkManifest` refuses a `connection` property, `optionsFrom`,
|
|
48
|
+
`action.connection`, a `trigger` and `auth.provider`). A step reaches an API with
|
|
49
|
+
the key a user enters in a `secret` property.
|
|
50
|
+
|
|
51
|
+
## `simulate`
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { simulate } from "@visualautomate/plugin-sdk"
|
|
55
|
+
import plugin from "./plugin"
|
|
56
|
+
|
|
57
|
+
const result = await simulate(plugin, {
|
|
58
|
+
config: { path: "notes/a.txt" },
|
|
59
|
+
storage: { "notes/a.txt": "hello" },
|
|
60
|
+
state: { runs: 0 },
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
result.status // "success" | "error" | "timeout" | ...
|
|
64
|
+
result.output?.data
|
|
65
|
+
result.files // what storage holds now
|
|
66
|
+
result.state // what state holds now
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Storage and state are real, in memory, with the same edge cases and limits the
|
|
70
|
+
platform's have — `get` on a missing key is `null`, `put` takes a string or bytes,
|
|
71
|
+
`list` is prefix-filtered, a state value over 128 KB or a `ttlSeconds` under 60 is
|
|
72
|
+
refused, and an expired value reads as `null`.
|
|
73
|
+
|
|
74
|
+
`media` is not simulated: `context.media` fails with a sentence saying so. Stub
|
|
75
|
+
`fetch` to test the code around an API call; asserting on what your plugin
|
|
76
|
+
*asked* for is a better test than any imitation of the API would be.
|
|
89
77
|
|
|
90
78
|
## The platform's code checks
|
|
91
79
|
|
|
@@ -104,7 +92,50 @@ check.packages // the allowed packages this code requires
|
|
|
104
92
|
version the sandbox has installed. Nothing else is available: no `fs`, no
|
|
105
93
|
`http`, and no subpaths like `lodash/get`.
|
|
106
94
|
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
`validatePluginCode` also refuses code over 100,000 characters, as the upload does.
|
|
96
|
+
|
|
97
|
+
## Loading code the way the runner does
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { executeOf, loadPluginCode, sandboxRequire, simulate } from "@visualautomate/plugin-sdk"
|
|
101
|
+
|
|
102
|
+
const exported = loadPluginCode(code, sandboxRequire((name) => require(name)), {
|
|
103
|
+
fetch: myGuardedFetch, // optional: the step's fetch
|
|
104
|
+
console: { log: (...a) => logs.push(a) },
|
|
105
|
+
})
|
|
106
|
+
const result = await simulate(executeOf(exported), { config })
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`loadPluginCode` compiles the code into a `node:vm` context of its own, wrapped
|
|
110
|
+
as the platform's runner wraps it, with the platform's globals (`fetch`, `btoa`,
|
|
111
|
+
`TextEncoder`, `Blob`, `FormData`, the typed arrays …) and no others: `Buffer`,
|
|
112
|
+
`process`, `globalThis`, `structuredClone` and the Node modules are undefined,
|
|
113
|
+
and `console` has `log`, `error`, `warn` and `info`. `executeOf` finds the
|
|
114
|
+
function the runner would call: the export itself, its `execute`, or a `default`
|
|
115
|
+
that is a function.
|
|
116
|
+
|
|
117
|
+
Objects a step returns belong to its own context, so compare them as JSON rather
|
|
118
|
+
than with a deep-equality that checks prototypes.
|
|
119
|
+
|
|
120
|
+
## Checking a manifest
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { checkManifest } from "@visualautomate/plugin-sdk"
|
|
124
|
+
|
|
125
|
+
const check = checkManifest(JSON.parse(manifestText), /* has code */ true)
|
|
126
|
+
check.ok // false when the portal would refuse it
|
|
127
|
+
check.kind // "code" | "declared" | "outputs-only" | "trigger"
|
|
128
|
+
check.errors // why it would be refused
|
|
129
|
+
check.warnings // what it would drop or regret
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The SDK's own reading of the rules the portal enforces, for code steps and
|
|
133
|
+
declared steps (`action`), and the refusal of anything that would act as a
|
|
134
|
+
connected account (`accountUseIn` lists each way in); the portal's word is final.
|
|
135
|
+
|
|
136
|
+
## The platform's facts
|
|
137
|
+
|
|
138
|
+
`PLATFORM_NODE_MAJOR`, `PROVIDED_GLOBALS`, `BLOCKED_GLOBAL_NAMES`,
|
|
139
|
+
`SANDBOX_TIERS`, `RETIRED_TIERS`, `STATE_LIMITS`, `SIGNED_URL_SECONDS`,
|
|
140
|
+
`MAX_CODE_LENGTH`, `SANDBOX_OVERRIDES` and `PUSH_CHECK` (the portal's default
|
|
141
|
+
`expectOutput` and time limits) are exported, generated from the platform.
|