Extensions
An extension is a WASM plugin that allows you to extend moon with additional functionality, have whitelisted access to the file system, and receive partial information about the current workspace. Extensions are extremely useful in offering new and unique functionality that doesn't need to be built into moon's core. It also enables the community to build and share their own extensions!
Using extensions
Before an extension can be executed with the moon ext
command, it must be
configured with the extensions
setting in
.moon/workspace.yml
(excluding built-in's).
extensions:
example:
plugin: 'https://example.com/path/to/example.wasm'
Once configured, it can be executed with moon ext
by name. Arguments unique to
the extension must be passed after a --
separator.
$ moon ext example -- --arg1 --arg2
Built-in extensions
moon is shipped with a few built-in extensions that are configured and enabled by default. Official moon extensions are built and published in our moonrepo/moon-extensions repository.
download
The download
extension can be used to download a file from a URL into the current workspace, as
defined by the --url
argument. For example, say we want to download the latest proto
binary:
$ moon ext download --\
--url https://github.com/moonrepo/proto/releases/latest/download/proto_cli-aarch64-apple-darwin.tar.xz
By default this will download proto_cli-aarch64-apple-darwin.tar.xz
into the current working
directory. To customize the location, use the --dest
argument. However, do note that the
destination must be within the current moon workspace, as only certain directories are whitelisted
for WASM.
$ moon ext download --\
--url https://github.com/moonrepo/proto/releases/latest/download/proto_cli-aarch64-apple-darwin.tar.xz\
--dest ./temp
Arguments
--url
(required) - URL of a file to download.--dest
- Destination folder to save the file. Defaults to the current working directory.--name
- Override the file name. Defaults to the file name in the URL.
migrate-nx
v1.22.0
This extension is currently experimental and will be improved over time.
The migrate-nx
extension can be used to migrate an Nx powered repository to moon. This process
will convert the root nx.json
and workspace.json
files, and any project.json
and
package.json
files found within the repository. The following changes are made:
- Migrates
targetDefaults
as global tasks to.moon/tasks/node.yml
(orbun.yml
),namedInputs
as file groups,workspaceLayout
as projects, and more. - Migrates all
project.json
settings tomoon.yml
equivalent settings. Target to task conversion assumes the following:- Target
executor
will be removed, and we'll attempt to extract the appropriate npm package command. For example,@nx/webpack:build
->webpack build
. - Target
options
will be converted to taskargs
. - The
{projectRoot}
and{workspaceRoot}
interpolations will be replaced with moon tokens.
- Target
$ moon ext migrate-nx
Nx and moon are quite different, so many settings are either ignored when converting, or are not a 1:1 conversion. We do our best to convert as much as possible, but some manual patching will most likely be required! We suggest testing each converted task 1-by-1 to ensure it works as expected.
Arguments
--bun
- Migrate to Bun based commands instead of Node.js.
Unsupported
The following features are not supported in moon, and are ignored when converting.
- Most settings in
nx.json
. - Named input variants: external dependencies, dependent task output files, dependent project inputs, or runtime commands.
- Target
configurations
anddefaultConfiguration
. Another task will be created instead that usesextends
. - Project
root
andsourceRoot
.
migrate-turborepo
v1.21.0
The migrate-turborepo
extension can be used to migrate a Turborepo powered repository to moon.
This process will convert the root turbo.json
file, and any turbo.json
files found within the
repository. The following changes are made:
- Migrates
pipeline
(v1) andtasks
(v2) global tasks to.moon/tasks/node.yml
(orbun.yml
) and project scoped tasks tomoon.yml
. Task commands will executepackage.json
scripts through a package manager. - Migrates root
global*
settings to.moon/tasks/node.yml
(orbun.yml
) asimplicitInputs
.
$ moon ext migrate-turborepo
Arguments
--bun
- Migrate to Bun based commands instead of Node.js.
Creating an extension
Refer to our official WASM guide for more information on how our WASM plugins work, critical concepts to know, how to create a plugin, and more. Once you have a good understanding, you may continue this specific guide.
Refer to our moonrepo/moon-extensions repository for in-depth examples.
Registering metadata
Before we begin, we must implement the register_extension
function, which simply provides some
metadata that we can bubble up to users, or to use for deeper integrations.
use extism_pdk::*;
use moon_pdk::*;
#[plugin_fn]
pub fn register_extension(Json(input): Json<ExtensionMetadataInput>) -> FnResult<Json<ExtensionMetadataOutput>> {
Ok(Json(ExtensionMetadataOutput {
name: "Extension name".into(),
description: Some("A description about what the extension does.".into()),
plugin_version: env!("CARGO_PKG_VERSION").into(),
..ExtensionMetadataOutput::default()
}))
}
Configuration schema
If you are using configuration, you can register the shape of the
configuration using the schematic
crate. This shape will be
used to generate outputs such as JSON schemas, or TypeScript types.
#[plugin_fn]
pub fn register_extension(_: ()) -> FnResult<Json<ExtensionMetadataOutput>> {
Ok(Json(ExtensionMetadataOutput {
// ...
config_schema: Some(schematic::SchemaBuilder::generate::<NodeConfig>()),
}))
}
Schematic is a heavy library, so we suggest adding the dependency like so:
[dependencies]
schematic = { version = "*", default-features = false, features = ["schema"] }
Implementing execution
Extensions support a single plugin function, execute_extension
, which is called by the
moon ext
command to execute the extension. This is where all your business
logic will reside.
#[host_fn]
extern "ExtismHost" {
fn host_log(input: Json<HostLogInput>);
}
#[plugin_fn]
pub fn execute_extension(Json(input): Json<ExecuteExtensionInput>) -> FnResult<()> {
host_log!(stdout, "Executing extension!");
Ok(())
}
Supporting arguments
Most extensions will require arguments, as it provides a mechanism for users to pass information
into the WASM runtime. To parse arguments, we provide the
Args
trait/macro from the
clap crate. Refer to their
official documentation on usage (we don't
support everything).
use moon_pdk::*;
#[derive(Args)]
pub struct ExampleExtensionArgs {
// --url, -u
#[arg(long, short = 'u', required = true)]
pub url: String,
}
Once your struct has been defined, you can parse the provided input arguments using the
parse_args
function.
#[plugin_fn]
pub fn execute_extension(Json(input): Json<ExecuteExtensionInput>) -> FnResult<()> {
let args = parse_args::<ExampleExtensionArgs>(&input.args)?;
args.url; // --url
Ok(())
}
Supporting configuration
Users can configure extensions with additional settings in
.moon/workspace.yml
. Do note that settings should be in camelCase for them
to be parsed correctly!
extensions:
example:
plugin: 'file://./path/to/example.wasm'
someSetting: 'abc'
anotherSetting: 123
In the plugin, we can map these settings (excluding plugin
) into a struct. The Default
trait
must be implemented to handle situations where settings were not configured, or some are missing.
config_struct!(
#[derive(Default)]
pub struct ExampleExtensionConfig {
pub some_setting: String,
pub another_setting: u32,
}
);
Once your struct has been defined, you can access the configuration using the
get_extension_config
function.
#[plugin_fn]
pub fn execute_extension(Json(input): Json<ExecuteExtensionInput>) -> FnResult<()> {
let config = get_extension_config::<ExampleExtensionConfig>()?;
config.another_setting; // 123
Ok(())
}