Adobe I/O CLI plugin (@adobe/aio-cli-plugin-api-mesh) for creating and managing API Mesh configurations.
This project is an oclif plugin that extends the Adobe I/O CLI (aio). It lets developers create, update, and locally run API Meshes — GraphQL gateways that federate multiple upstream APIs into a single endpoint, powered by Cloudflare Workers.
How it works end-to-end:
- The user writes a
mesh.jsonfile describing sources (REST, GraphQL, SOAP), transforms, hooks, and plugins. - The CLI validates and submits that config to the Schema Management Service (SMS) — the Adobe-hosted backend that builds and deploys the mesh to Cloudflare.
- Once provisioned, the mesh is reachable at a Cloudflare-hosted GraphQL endpoint.
- For local development, the CLI builds the mesh artifact with
@adobe-apimesh/mesh-builderand runs it locally via Wrangler (Cloudflare Workers runtime).
URL constants for stage and prod live in src/constants.js. Switch to stage with aio config set cli.env stage; each constant can also be overridden by a matching environment variable.
| Term | Meaning |
|---|---|
| Mesh | A GraphQL gateway config combining multiple upstream sources |
| Mesh ID | Unique identifier assigned by SMS on create, tied to org+project+workspace |
| Mesh Config | The meshConfig block in the user's JSON file — sources, transforms, plugins |
| Mesh Artifact | Compiled output of mesh-builder — stored in mesh-artifact/<meshId>/ during run |
.mesh/ |
Packaged artifact directory read by Wrangler during local dev |
| Tenant Files | JS hook/plugin files referenced in the mesh config — bundled into tenantFiles/ |
| Ray ID | Per-request unique ID on the Cloudflare edge — used to look up individual logs |
| Secrets | YAML key-value pairs, encrypted client-side with the org's RSA public key before upload |
| Log Forwarding | Routing mesh request logs to an external destination (e.g. New Relic) |
| SMS | Schema Management Service — Adobe's backend for mesh CRUD and deployment |
| Placeholder interpolation | ${VAR} syntax in mesh.json resolved against a .env file before submission |
src/lib/smsClient.js — all HTTP calls to SMS and Adobe Developer Console go through here. If SMS behaviour changes, start here.
src/helpers.js — initSdk() is the entry point for every command. Resolves the IMS token, looks up org/project/workspace via Dev Console, and caches the result.
Two separate responsibilities — easy to confuse:
src/secrets.js— runtime: parses the encrypted secrets JSON from theSECRETSenv var inside the running worker. A Proxy (getSecretsHandler) throws on access to undefined keys and blocks mutation.src/utils.js— build-time:encryptSecrets()encrypts secrets with the org's RSA public key;validateSecretsFile()reads and validates the YAML secrets file before submission.
src/fixPlugins.js — rewrites the compiled mesh index.js to redirect @graphql-mesh/plugin-http-details-extensions imports to a local fork. Run as a post-build step. Without this the worker fails at runtime with a missing module error.
- Every command calls
initSdk()first to resolve org/project/workspace context. - Destructive commands (
delete,update,cache:purge, log-forwarding changes) always prompt for confirmation unless--autoConfirmAction(-c) is passed. - Commands return a plain object from
run()— oclif serialises it when--jsonis set. The return shape is consumed by downstream generators (generator-app-api-mesh), so don't rename return keys without checking what depends on them. - Error messages always append
RequestId: ${global.requestId}to aid support tracing.
Most files are CommonJS (require/module.exports). src/worker.js and src/state.js are ESM (import/export) because they run inside the Cloudflare Workers runtime, which requires ESM. Do not mix formats within a file.
Tests mock src/helpers.js and src/lib/smsClient.js entirely with jest.mock() — no real network calls. Fixtures live in src/commands/__fixtures__/. New shared flags go in src/utils.js, not inline in the command file.
The pino logger (src/classes/logger.js) is off by default. Enable with ENABLE_LOGGER=true; set level with LOG_LEVEL (default info). Use logger.info/debug/error, not console.log, except in src/worker.js and src/fixPlugins.js which run outside the logger context.
Secrets files are YAML. Limits (max count and max size) are enforced in src/constants.js. Secrets are encrypted client-side before transmission — the plain-text YAML file must never be committed or logged.
Commands return a plain object from run(). generator-app-api-mesh and other downstream tools depend on specific key names in these return objects. Renaming a return key (e.g. meshId → id) is a silent breaking change for those consumers. Check for downstream usage before renaming.
Without --select, run builds the mesh artifact locally from the provided mesh.json. With --select, it downloads the currently deployed remote artifact and runs that instead — no local build. The two modes behave very differently; passing --select with a local mesh.json file will silently ignore the file.
initSdk() writes the resolved org/project/workspace to a local cache file. Subsequent command invocations reuse the cached values without prompting. If the context seems wrong (wrong org, wrong project), pass --ignoreCache (-i) to force re-selection. The cache persists across terminal sessions.
interpolateMesh() replaces ${VAR} tokens in mesh.json using the .env file. If a variable is referenced in mesh.json but missing from .env, the token is replaced with an empty string without error. The mesh will be submitted with an empty value, which may cause a build failure in SMS with a misleading error.