@visualautomate/plugin-sdk 0.6.0 → 0.7.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 +136 -92
- package/dist/index.cjs +637 -203
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +627 -203
- package/dist/index.mjs.map +3 -3
- package/dist/manifest-check.d.ts +16 -0
- package/dist/platform-rules.d.ts +52 -5
- package/dist/runtime.d.ts +42 -0
- package/dist/types.d.ts +76 -4
- package/dist/validate.d.ts +0 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,91 +1,93 @@
|
|
|
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
|
-
| `connections` | the accounts this step was configured with. Absent when it declares none |
|
|
45
|
-
| `workflowId`, `nodeId`, `userId`, `executionId`, `startedAt`, `secrets` | about the run |
|
|
46
|
-
|
|
47
|
-
You never see a credential and you never name a host. `connections.get("account")`
|
|
48
|
-
takes the name of one of *your* properties, and the platform attaches the token
|
|
49
|
-
on the way past — which is what stops a plugin pointing somebody's account
|
|
50
|
-
somewhere it chose.
|
|
51
|
-
|
|
52
|
-
## `simulate`
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { simulate } from "@visualautomate/plugin-sdk"
|
|
56
|
-
import plugin from "./plugin"
|
|
57
|
-
|
|
58
|
-
const result = await simulate(plugin, {
|
|
59
|
-
config: { path: "notes/a.txt" },
|
|
60
|
-
storage: { "notes/a.txt": "hello" },
|
|
61
|
-
state: { runs: 0 },
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
result.status // "success" | "error" | "timeout" | ...
|
|
65
|
-
result.output?.data
|
|
66
|
-
result.files // what storage holds now
|
|
67
|
-
result.state // what state holds now
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Storage and state are real, in memory, with the same edge cases the
|
|
71
|
-
have — `get` on a missing key is `null`, `put` takes a string or bytes,
|
|
72
|
-
is prefix-filtered
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
| `connections` | the accounts this step was configured with. Absent when it declares none, or none was chosen |
|
|
45
|
+
| `workflowId`, `nodeId`, `userId`, `executionId`, `startedAt`, `secrets` | about the run |
|
|
46
|
+
|
|
47
|
+
You never see a credential and you never name a host. `connections.get("account")`
|
|
48
|
+
takes the name of one of *your* properties, and the platform attaches the token
|
|
49
|
+
on the way past — which is what stops a plugin pointing somebody's account
|
|
50
|
+
somewhere it chose.
|
|
51
|
+
|
|
52
|
+
## `simulate`
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { simulate } from "@visualautomate/plugin-sdk"
|
|
56
|
+
import plugin from "./plugin"
|
|
57
|
+
|
|
58
|
+
const result = await simulate(plugin, {
|
|
59
|
+
config: { path: "notes/a.txt" },
|
|
60
|
+
storage: { "notes/a.txt": "hello" },
|
|
61
|
+
state: { runs: 0 },
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
result.status // "success" | "error" | "timeout" | ...
|
|
65
|
+
result.output?.data
|
|
66
|
+
result.files // what storage holds now
|
|
67
|
+
result.state // what state holds now
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Storage and state are real, in memory, with the same edge cases and limits the
|
|
71
|
+
platform's have — `get` on a missing key is `null`, `put` takes a string or bytes,
|
|
72
|
+
`list` is prefix-filtered, a state value over 128 KB or a `ttlSeconds` under 60 is
|
|
73
|
+
refused, and an expired value reads as `null`.
|
|
74
|
+
|
|
75
|
+
`connections` and `media` are not simulated. Without a stub, `context.connections`
|
|
76
|
+
is absent, as on the platform when no account was chosen, and `context.media`
|
|
77
|
+
fails with a sentence saying so. Pass a stub to test the code around them:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await simulate(plugin, {
|
|
81
|
+
connections: {
|
|
82
|
+
async get() {
|
|
83
|
+
return { id: "test", async request() { return { ok: true, status: 200, body: {} } } }
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Asserting on what your plugin *asked* the provider for is a better test than any
|
|
90
|
+
imitation of the provider would be.
|
|
89
91
|
|
|
90
92
|
## The platform's code checks
|
|
91
93
|
|
|
@@ -104,7 +106,49 @@ check.packages // the allowed packages this code requires
|
|
|
104
106
|
version the sandbox has installed. Nothing else is available: no `fs`, no
|
|
105
107
|
`http`, and no subpaths like `lodash/get`.
|
|
106
108
|
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
`validatePluginCode` also refuses code over 100,000 characters, as the upload does.
|
|
110
|
+
|
|
111
|
+
## Loading code the way the runner does
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { executeOf, loadPluginCode, sandboxRequire, simulate } from "@visualautomate/plugin-sdk"
|
|
115
|
+
|
|
116
|
+
const exported = loadPluginCode(code, sandboxRequire((name) => require(name)), {
|
|
117
|
+
fetch: myGuardedFetch, // optional: the step's fetch
|
|
118
|
+
console: { log: (...a) => logs.push(a) },
|
|
119
|
+
})
|
|
120
|
+
const result = await simulate(executeOf(exported), { config })
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`loadPluginCode` compiles the code into a `node:vm` context of its own, wrapped
|
|
124
|
+
as the platform's runner wraps it, with the platform's globals (`fetch`, `btoa`,
|
|
125
|
+
`TextEncoder`, `Blob`, `FormData`, the typed arrays …) and no others: `Buffer`,
|
|
126
|
+
`process`, `globalThis`, `structuredClone` and the Node modules are undefined,
|
|
127
|
+
and `console` has `log`, `error`, `warn` and `info`. `executeOf` finds the
|
|
128
|
+
function the runner would call: the export itself, its `execute`, or a `default`
|
|
129
|
+
that is a function.
|
|
130
|
+
|
|
131
|
+
Objects a step returns belong to its own context, so compare them as JSON rather
|
|
132
|
+
than with a deep-equality that checks prototypes.
|
|
133
|
+
|
|
134
|
+
## Checking a manifest
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { checkManifest } from "@visualautomate/plugin-sdk"
|
|
138
|
+
|
|
139
|
+
const check = checkManifest(JSON.parse(manifestText), /* has code */ true)
|
|
140
|
+
check.ok // false when the portal would refuse it
|
|
141
|
+
check.kind // "code" | "declared" | "outputs-only" | "trigger"
|
|
142
|
+
check.errors // why it would be refused
|
|
143
|
+
check.warnings // what it would drop or regret
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The SDK's own reading of the rules the portal enforces, for code steps,
|
|
147
|
+
declared steps (`action`), triggers and `optionsFrom`; the portal's word is final.
|
|
148
|
+
|
|
149
|
+
## The platform's facts
|
|
150
|
+
|
|
151
|
+
`PLATFORM_NODE_MAJOR`, `PROVIDED_GLOBALS`, `BLOCKED_GLOBAL_NAMES`,
|
|
152
|
+
`SANDBOX_TIERS`, `RETIRED_TIERS`, `STATE_LIMITS`, `SIGNED_URL_SECONDS`,
|
|
153
|
+
`MAX_CODE_LENGTH`, `SANDBOX_OVERRIDES` and `PUSH_CHECK` (the portal's default
|
|
154
|
+
`expectOutput` and time limits) are exported, generated from the platform.
|