@junoflow/cli
v0.3.0
Published
Command line tools for developing Juno integrations
Readme
@junoflow/cli
Develop Juno integrations locally: scaffold a project, generate types from your manifest, check it, and deploy it.
npm install --save-dev @junoflow/cli
npx juno init my-integration --url http://juno.local:8080An integration is a git repository holding manifest.json and TypeScript. Juno
fetches it at a tag, compiles it, and runs it in a sandbox. There is no
packaging format and nothing is published to npm.
Commands
| Command | What it does |
| ----------------------- | ------------------------------------------------------------------------------ |
| juno init <name> | fetch a starting project from a Juno and write it down |
| juno types | POST manifest.json, write types/ |
| juno verify-types | check types/ still matches manifest.json — talks to nothing |
| juno check | local verify-types + tsc --noEmit + eslint, then validate against a Juno |
| juno deploy [--watch] | upload, verify, activate — and write back the returned types |
| juno pull | write back the manifest and sources the instance holds |
Options
| Flag | Meaning |
| --------------- | -------------------------------------------------------------------- |
| --url <url> | the instance to talk to |
| --name <name> | address an install under a different name than the manifest declares |
| --watch | redeploy on every save (deploy) |
| --force | overwrite a dirty working tree (pull) |
| --here | scaffold into the current directory (init) |
| --skip-local | skip the local verify-types/tsc/eslint pass (check) |
Choosing an instance
In order: --url, then $JUNO_URL, then "juno": { "target": … } in
package.json. A project downloaded from a Juno already has the third, so a
fresh clone deploys with no setup.
Juno has no authentication on any endpoint. An instance is a trusted-LAN appliance; do not expose one to the internet.
The loop
npm run watch # juno deploy --watchAdd an action to manifest.json. The types regenerate, and your editor
immediately red-squiggles src/integration.ts because IntegrationActions now
requires a method you have not written — with the parameter and return types
your JSON Schema implies. The manifest drives the types drive the compiler.
Why the types are committed
types/ is generated and checked in. CI needs no Juno, a fresh clone compiles
offline, and the diff in a pull request shows that adding an action changed the
interface. Most importantly, a drive-by contributor fixing a bug in someone
else's integration never needs an instance — only the person changing the
manifest does.
The cost of that decision is drift: edit manifest.json, forget
npm run types, and every other signal stays green while types/ describes a
contract you no longer declare. tsc compiles happily against stale types,
eslint is happy, and the testkit's conformance check compares the manifest
with your module, never with types/ — so the mismatch surfaces at deploy, or
later, as a flow author calling an action whose shape changed.
juno verify-types closes it. juno types records a fingerprint of the
manifest.json it generated from, as a comment in types/integration-env.d.ts:
// integration-env.d.ts for 'pushover'
// DO NOT EDIT — generated by 'juno types' from 'manifest.json'.
// juno:manifest-sha256 5c5ca8eabe12cf44verify-types recomputes it and compares. It runs first in npm test and
inside juno check, and it contacts nothing — which is the point: the mistake
is caught in a contributor's CI, and that CI deliberately has no instance to
ask. Reformatting manifest.json also invalidates the stamp; the fix is the
same npm run types either way.
What runs where
| | offline | needs an instance |
| --------------------------------------------------------------- | ------- | ----------------- |
| tsc --noEmit, eslint, vitest | ✅ | |
| juno verify-types (is types/ current?) | ✅ | |
| structural manifest check via "$schema" in your editor | ✅ | |
| reserved names, cross-surface collisions, JSON Schema compiling | | juno check |
| juno init, juno types, juno deploy | | ✅ |
Everything semantic lives in the Go server. This package knows some HTTP endpoints and a config preset — that is deliberate, so there is one answer to "is this manifest valid" rather than two that can disagree.
init vs. downloading a project
Both produce the same project, from the same generator on the instance: init
starts from a stub manifest, the Develop locally → Download project button
starts from an integration that already exists there. Neither is a local
template — the CLI writes the files it is handed, so a scaffold cannot drift
from what the server ejects. To refresh an existing checkout use juno pull; a
second download would clobber your working tree.
