///|
import {
"moonbitlang/core/env" @env,
"i5ting/gulp/entry" @entry,
}
///|
fn build(_ctx : @core.Context) -> Result[Unit, @core.MulpError] {
println("building...")
Ok(())
}
///|
fn main {
@entry.run_tasks([("build", build)], @env.args(), default_task="build")
}gulp
gulp build
gulp --tasks
gulp --tasks-simple
gulp --tree build
gulp --watch buildgulp --gulpfile path/to/gulp.mbtx build
gulp --file path/to/gulp.mbtx --tasks
gulp --gulpfile path/to/gulp.mbtx --tree buildmoon run cmd/gulp --target native -- --cwd examples/basic buildcd gulp
moon build cmd/gulp --release --target nativegulp/_build/native/release/build/cmd/gulp/gulp.exemkdir -p ~/.local/bin
cp gulp/_build/native/release/build/cmd/gulp/gulp.exe ~/.local/bin/gulp
chmod +x ~/.local/bin/gulp
export PATH="$HOME/.local/bin:$PATH"node gulp/scripts/release_smoke.mjsmoon run --target native <gulp.mbtx> -- <args>| Case | Status | Stream |
|---|---|---|
| --help, --version, -v, successful task, successful --tasks / --tasks-simple / -T / --tree | 0 | stdout |
| CLI usage error, missing gulp.mbtx, unreadable config, malformed legacy config | 1 | stderr |
| Unknown task reported by gulp.mbtx | script status, usually 1 | script stderr |
| --watch <task> where the script task fails | script status, usually 1 | script stderr |
| Explicit legacy entry or moon run process failure | child process status | child stdout/stderr |
let registry = new_registry()
let build = task("build", fn(ctx) -> Result[Unit, MulpError] {
if ctx.is_cancelled() {
Err(task_failed("build", "cancelled"))
} else {
Ok(())
}
})
registry.register(build).unwrap()
let pipeline = series([build])
pipeline.run(new_context(
cwd="/workspace",
now_ms=0L,
cancellation=new_cancellation_token(),
))let input = file(
cwd="/workspace",
base="/workspace/src",
path="/workspace/src/app.txt",
contents=Bytes(byte_stream(["hello"])),
)
let sink = memory_dest()
let pipeline = src([input])
.pipe(fn(source : File) -> FileStream {
let copy = file(
cwd=source.cwd,
base=source.base,
path=source.path + ".copy",
contents=source.contents,
)
file_stream([source, copy])
})
.pipe(dest("dist", sink))
let ctx = new_context(
cwd="/workspace",
now_ms=0L,
cancellation=new_cancellation_token(),
)
let outputs = pipeline.collect(ctx).unwrap()inspect(outputs.length(), content="2")
inspect(sink.contents_for("dist/app.txt").unwrap(), content="hello")let rename = @through2.object_transform_files(
map=Some(fn(file : @stream.File) -> Result[@stream.File?, @core.MulpError] {
Ok(Some(file.with_path(file.path + ".out")))
}),
)
let output = @stream.file_stream([input]).pipe_stream(rename)let copy_plugin = plugin("copy", fn(file : File) -> FileStream {
file_stream([file])
})
let output = src([input]).pipe_plugin(copy_plugin)let bundle = src([a, b]).pipe_stream(concat_plugin("bundle.txt"))moon bench --package stream --target nativemoon bench --package vinyl --target native
moon bench --package vinyl_fs --target nativenode gulp/scripts/bench_compare.mjs
node gulp/scripts/bench_compare.mjs --json
node gulp/scripts/bench_compare.mjs --scenario vinyl-fs-src-buffer-1000 --rounds 3
node gulp/scripts/bench_compare.mjs --scenario pipeline-memory-dest --rounds 3let command = moon_run_script_command("/workspace/app/gulp.mbtx", ["build"])let listing = render_cli_output(
parse_args(["--tasks"], default_task="build").unwrap(),
registry,
).unwrap()let result = dispatch_cli(
cwd="/workspace/app/src",
files=[("/workspace/app/gulp.mbtx", "fn main { println(\"build\") }")],
args=["build"],
registry=registry,
).unwrap()moon run --target native <gulp.mbtx> -- <task>import {
"moonbitlang/core/env" @env
}
fn selected_task(args : Array[String]) -> String {
if args.length() >= 2 {
args[1]
} else {
"build"
}
}
fn main {
println("running " + selected_task(@env.args()))
}///|
import {
"moonbitlang/core/env" @env,
"i5ting/gulp/entry" @entry,
}
///|
fn build(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
fn clean(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
fn watch(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
fn parallel_task(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
///|
fn main {
@entry.run_tasks(
[("build", build), ("clean", clean), ("watch", watch), ("parallel", parallel_task)],
@env.args(),
)
}let pipeline = @stream.src([input])
.pipe(@stream.map_files(fn(file : @stream.File) -> @stream.File {
{
cwd: file.cwd,
base: file.base,
path: file.path + ".copy",
contents: file.contents,
metadata: file.metadata,
}
}))
.pipe(@stream.file_dest("dist"))fn main {
let registry = @entry.new_registry()
let clean = @entry.task(registry, "clean", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
let build = @entry.task(registry, "build", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
let all = @core.series([clean, build])
ignore(@core.task(registry, "default", fn(ctx) { all.run(ctx) }))
@entry.run(registry, @env.args())
}gulp --watch buildexports.clean = clean;
exports.build = gulp.series(clean, scripts);
exports.default = exports.build;///|
import {
"moonbitlang/core/env" @env,
"i5ting/gulp/entry" @entry,
}
///|
fn clean(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
fn build(_ctx : @core.Context) -> Result[Unit, @core.MulpError] { Ok(()) }
///|
fn main {
@entry.run_tasks([("clean", clean), ("build", build)], @env.args())
}| gulp recipe | gulp shape |
|---|---|
| gulp.src(globs).pipe(plugin()).pipe(gulp.dest(outDir)) | Use @vinyl_fs.src(...), @through2.object_transform_vinyl(...), and @vinyl_fs.dest(...). |
| gulp.series(clean, gulp.parallel(styles, scripts)) | Register clean, styles, and scripts, then expose @entry.series([clean, @entry.parallel([styles, scripts])]). |
| gulp.watch(globs, task) | Use gulp --watch task from the CLI, or wire @platform_async.async_watch_loop(...) when embedding a watcher. |
| gulp.src(globs, { since: gulp.lastRun(task) }) | Use @core.last_run(...) / @core.last_run_async(...) with src(..., since=...). |
| Callback task function(done) { ...; done(err) } | Use @core.async_task_from_callback(...); duplicate completion is rejected. |
| Stream-returning task | Use @stream_async.async_task_from_stream(...) so the task completes when the stream drains or fails. |
| gulp --tasks-simple | Use gulp --tasks-simple; it is normalized to --tasks for gulp.mbtx scripts. |
| gulp --tasks / gulp --tree | Handled automatically by @entry.run_tasks(...) — no explicit dispatcher needed. |
| gulp CLI flag or behavior | gulp status | Notes |
|---|---|---|
| gulp default task | Supported as gulp | The selected default is defined by the gulp.mbtx dispatcher. |
| gulp <task> | Supported as gulp <task> | One task name is accepted by the native CLI and forwarded to gulp.mbtx. |
| gulp <task> <task> concurrent CLI tasks | Not supported yet | Use an explicit @entry.parallel([...]) task in gulp.mbtx. |
| -v / --version | Supported | Prints the native gulp version. |
| --cwd <dir> | Supported | Discovery and command execution use the supplied working directory. |
| --gulpfile <path> | MoonBit-compatible alias | Points to gulp.mbtx or explicit legacy gulp.mbt; it does not execute JS gulpfiles. |
| --file <path> / --gulpfile <path> | Supported | --gulpfile is the preferred native spelling. |
| --tasks-simple | Supported | Normalized to --tasks before script execution. |
| --tasks | Supported with gulp semantics | gulp expects the script to print the task list; gulp's decorated tree output is not cloned. |
| -T | Supported as an alias for --tasks | It lists tasks with current gulp semantics rather than gulp's decorated task tree. |
| --require <module> | Not supported | gulp.mbtx is MoonBit script mode, not a JS/transpiled gulpfile. |
| --verify | Not supported | npm plugin blacklist verification does not apply to native MoonBit plugins. |
| --color / --no-color / --silent | Not supported yet | Current output is plain stdout/stderr from the script and native command. |
exports.build = gulp.series(clean, gulp.parallel(styles, scripts));
exports.assets = function assets() {
return src("src/**/*").pipe(dest("dist"));
};
exports.deploy = function deploy(done) {
upload(function (err) {
done(err);
});
};let clean = @entry.async_task(registry, "clean", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
let styles = @entry.async_task(registry, "styles", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
let scripts = @entry.async_task(registry, "scripts", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
ignore(@entry.async_task_from(
registry,
"build",
@entry.series([clean, @entry.parallel([styles, scripts])]),
))let assets = @stream_async.async_task_from_stream(
"assets",
@stream.src(files).pipe(@stream.dest("dist", sink)),
)let deploy = @core.async_task_from_callback("deploy", async fn(done) -> Unit {
@async.sleep(10)
done(Ok(()))
})| JS through2 pattern | MoonBit through2 shape |
|---|---|
| through2() passthrough | @through2.transform_bytes() |
| through2.obj(fn) | @through2.object_transform_files(map=Some(fn)) or @through2.object_transform_vinyl(map=Some(fn)) |
| through2.ctor(fn) | @through2.ctor_files(fn) / @through2.ctor_vinyl(fn) / @through2.ctor_bytes(fn) |
| cb(null, chunk) | Ok(Some(chunk)) |
| no output / filtered chunk | Ok(None) |
| this.push(a); this.push(b); cb() | Ok([a, b]) via *_flat helpers |
| _flush(cb) | flush=Some(fn() -> Result[Array[_], @core.MulpError] { ... }) |
| cb(err) / thrown error | Err(@core.stream_error("message")) |
| gulp API | gulp API | Notes |
|---|---|---|
| src(globs, options) | @vinyl_fs.src(globs, @vinyl_fs.src_options(...)) or lower-level @stream.src(files) | vinyl_fs reads the native filesystem and preserves Vinyl-style cwd, base, relative, metadata, buffered, and streaming contents. allow_empty, remove_bom, encoding=false, and sourcemaps=true map to gulp's allowEmpty, removeBOM, raw byte, and adjacent .map loading behavior. |
| dest(outDir, options) | @vinyl_fs.dest(outDir) / @vinyl_fs.dest_with_options(...) or lower-level @stream.file_dest(...) | Writes real files and returns files with updated destination paths. mode, dir_mode, and mtime_ms metadata are applied to native filesystem entries; overwrite=false, append=true, external sourcemaps=Some("."), and inline_sourcemaps=true cover the matching vinyl-fs write modes. |
| symlink(outDir, options) | @vinyl_fs.symlink(outDir) / @vinyl_fs.symlink_with_options(...) | Emits filesystem symlinks where the native platform supports them; relative_symlinks=true maps to vinyl-fs relativeSymlinks, and overwrite=false preserves an existing destination link. |
| new Vinyl({ sourceMap }) | @vinyl.vinyl_file(source_map=...) | MoonBit keeps custom JS-style props in typed fields or metadata; source_map is first class. |
| task(name, fn) | @entry.run_tasks([("name", fn), ...], args) — or @entry.task(registry, ...) when task handles are needed for series/parallel. | run_tasks is the primary pattern for gulp.mbtx. |
| series(...) | @entry.series([...]) / @core.series([...]) | Preserves left-to-right task order. |
| parallel(...) | @entry.parallel([...]) / @core.async_parallel([...]) | Native async tasks run concurrently with shared cancellation. |
| watch(globs, task) | @platform_async.async_watch_loop(...) plus CLI watch dispatch | Event normalization and pending rebuild behavior are implemented; native OS event backends are still a TODO. |
| lastRun(task) | @core.last_run(...) / @core.last_run_async(...) | Integrates with src(..., since=...) style filtering. |
| callback completion | @core.async_task_from_callback(...) | Duplicate completion and mixed completion modes are rejected. |
| stream completion | @stream_async.async_task_from_stream(...) | A stream task completes when the stream drains or fails. |
gulp.toml entry package -> gulp.mbtx script
gulp.toml task metadata -> --tasks / --tree output from gulp.mbtx
moon run <entry> -- <task> -> moon run --target native <gulp.mbtx> -- <task>pub fn register_tasks(registry : @core.Registry) -> Unit {
ignore(@entry.task(registry, "build", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
}))
}let registry = @entry.new_registry()
register_tasks(registry)fn main {
let registry = @entry.new_registry()
register_tasks(registry)
let ctx = @core.new_context(
cwd=".",
now_ms=0L,
cancellation=@core.new_cancellation_token(),
)
@entry.run_args(registry, @env.args(), ctx).unwrap()
}let output = @entry.render_args(registry, ["build", "--tasks"]).unwrap()async fn main {
let registry = @entry.new_async_registry()
let clean = @entry.async_task(registry, "clean", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
let scripts = @entry.async_task(registry, "scripts", fn(_ctx) -> Result[Unit, @core.MulpError] {
Ok(())
})
ignore(@entry.async_task_from(registry, "build", @core.async_parallel([clean, scripts])))
let ctx = @core.new_context(
cwd=".",
now_ms=0L,
cancellation=@core.new_cancellation_token(),
)
@entry.async_run_entry_args(registry, @env.args(), ctx, fn(output) {
println(output)
}).unwrap()
}