@visualautomate/cli
v0.6.1
Published
Build and test VisualAutomate plugins.
Maintainers
Readme
@visualautomate/cli
Build and test VisualAutomate plugins.
npm install -g @visualautomate/cli
vsa init my-step
cd my-step
vsa dev # the sandbox, and a run again on every saveNothing here signs in. Your plugin reaches the platform through its GitHub repository: push a commit, the tests run there, and Publish on the portal takes it from the commit that passed.
va and visualautomate are the same command.
That is the whole toolchain. There is nothing else to install: the CLI carries
the SDK inside it, compiles your TypeScript itself, and writes the type
declarations into the project. A plugin directory has no node_modules, no
build step and no dependencies.
What init gives you
plugin.ts your code
manifest.json what the canvas shows, with the rest of the options
written out as commented examples
plugin.json name, version, description, category
test.json the input, config, storage and state `test` runs with
visualautomate.d.ts the types, written in rather than installed
tsconfig.json so your editor type-checks itTemplates: blank, http-request, connection, file. Pass one with
--template. Each runs unmodified.
Types in your editor
Your plugin's code imports nothing — the platform refuses the word import
anywhere in the file, comments included, and the SDK is not a package a step may
require. So the types are written into your project instead, and one line names
them:
/** @type {PluginModule} */
module.exports = {
execute: async (input, config, context) => {
// ^ completes, with the documentation
},
}dev and test write visualautomate.d.ts and a jsconfig.json beside your
code and keep them current; vsa types does it on its own. In an app's
repository they go at the root, where they cover every module. Nothing is
installed, nothing of it exists while your plugin runs, and a file you wrote
yourself is never written over — delete ours and it comes back.
add:property
A field on the node — what somebody fills in when they drop your module into a
workflow — written into manifest.json:
vsa add:property string apiKey "The key to call with"
vsa add:property number retries "How many times to try again" 2 --required
vsa add:property select mode "Which way round" fast --options "Fast:fast,Thorough:thorough"<type> <name> [description] [default], where the name is what your code reads:
config.apiKey. The label is made from the name unless --label says otherwise,
and the default is stored as its type has it, so 2 is a number and false is a
boolean.
Types: string, number, boolean, select, multiselect, json, color,
date, connection, file, path, paths. A select or multiselect needs
--options, and a connection needs --provider <name>.
The same field is added to visualautomate.test.json under config, with its
default or a value of the right shape, so the next vsa test passes something
for it instead of nothing. A value you have already put there is kept.
At the root of an app's repository, --module <name> says which one — the same
short names test and dev take. The manifest is edited where it stands: your
comments, the examples underneath and your own formatting stay as they are.
test
Runs your plugin the way the platform would, in three steps:
- The platform's code checks. The same rules the upload applies: only
allowed packages in
require, no dynamicrequire, none of the refused patterns (eval,process.env,import, …). Code that fails here would be refused on push, and says why. - The sandbox's
require. A step gets the allowed npm packages and nothing else — nofs, nohttp. Those packages are installed in the sandbox; here they come from your project, and a missing one prints thenpm installcommand for the version the sandbox has. - A context that behaves.
storageandstateare real, in memory, and the result shows what the step left behind:
✓ passed success, 3ms
output success (expected success)
data { "path": "notes/hello-shouted.txt", "size": 17 }
files notes/hello.txt, notes/hello-shouted.txt
state {"runs":1}A run passes when it succeeds and, if the test file names an expectOutput,
leaves by that port — the same verdict as the GitHub check.
The verdict is green when it passed and red when it did not, in the sandbox as
well as out here. NO_COLOR turns that off, and output that is not a terminal —
a pipe, a file, a CI log — is plain anyway; FORCE_COLOR turns it back on.
In a GitHub repository
test works wherever the platform tests your code on a push:
my-plugin/ a plugin's repository
manifest.json
index.js
visualautomate.test.json
my-app/ an app's repository
modules/
send-message/
manifest.json
index.js
visualautomate.test.json
list-channels/
…At the root of an app's repository every module runs, one after another. Name one
to run only that: vsa test send-message, or any short name that picks it out —
vsa test send. --module <name> is the same thing. index.js in a repository is run as written,
unbundled, because that is the file the sandbox loads.
dev
vsa dev # every module in modules/
vsa dev send-message # one of them; the shortest name that picks it will do
vsa dev --tier boosted # a bigger machine
vsa dev --local # on this machine's Node, without the sandboxEverything in one command: it starts Docker if it is not running, fetches the sandbox image the first time, runs every module, and runs them again on every save. Only the module whose folder changed runs again; a change anywhere else runs them all. Ctrl+C stops it.
context.connections and context.media are not simulated. One is
somebody's OAuth account and the other is an ffmpeg container; pretending to be
either would produce a plugin that passes here and fails on a canvas. Asking for
one fails with a sentence saying so.
visualautomate.test.json (or test.json, or --input <file>) seeds the run:
{
"config": { "source": "notes/hello.txt" },
"input": {},
"storage": { "notes/hello.txt": "hello from a file" },
"state": { "runs": 0 },
"expectOutput": "success",
"timeoutMs": 30000,
"allowedDomains": ["api.example.com"]
}The platform's push check reads config, input, expectOutput and
timeoutMs; storage, state, secrets and allowedDomains are for running
locally. A file that is not valid JSON stops the run rather than running with
nothing.
Where fetch may go
On the platform a step may contact the domains its plugin declares, plus any host in the configuration filled in for that step. A declared domain covers its subdomains; localhost, private networks and metadata addresses are never allowed.
Put the domains in allowedDomains and test applies the same rule: a request
anywhere else fails. Without allowedDomains nothing is blocked, but every host
the platform would refuse is listed after the run.
--persist
Keeps storage and state between runs, in .visualautomate/ next to the plugin:
.visualautomate/storage/<path> files the step wrote
.visualautomate/state.json the workflow stateOnce that folder exists it is used without the flag. Delete it to start over,
and add it to .gitignore.
The sandbox on your machine
dev uses it by default; test does with --docker:
vsa dev
vsa test --docker
vsa dev --tier boostedYour plugin runs inside the plugin sandbox image,
ghcr.io/visualautomate/plugin-sandbox, at the version of this CLI:
- the Node version the platform runs plugins on, with every allowed npm
package installed — no
npm installneeded; - the CPU and memory of the sandbox tier:
standard(¼ vCPU, 1 GB, the default),boosted(½ vCPU, 4 GB),high(1 vCPU, 6 GB),max(2 vCPU, 8 GB); - no Linux capabilities, a read-only filesystem apart from your project and a
small
/tmp, and nothing else from your machine.
A step that runs out of memory or time here does the same on the platform.
Docker does not have to be running: dev starts Docker Desktop and waits for it,
then fetches the image if this machine does not have it. --image uses a
different image, for example one you built yourself. The watcher stays on your
machine, and each run starts a fresh container.
context.media is not available locally, in or out of Docker.
Publishing
Not from here. Connect the repository to your app on the portal, push your
commit, and the platform runs the same checks vsa test runs against every
module it touched. Publish on the portal takes the code from a commit that
passed, and a version snapshot is saved so you can roll back.
Comments in manifest.json are a convenience of this tool: the file on your
disk may have them, and they are stripped before the manifest is read.
Where things are
The runtime API your plugin is written against is documented in the types
(visualautomate.d.ts in your project, @visualautomate/plugin-sdk if you
prefer to install it) and on the platform's own docs page.
