Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion uzumibi-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uzumibi-cli"
version = "0.6.0-rc1"
version = "0.6.0-rc2"
edition = "2024"
authors = ["Uchio Kondo <udzura@udzura.jp>"]
description = "Uzumibi CLI tool to generate serverless mruby/edge apps"
Expand Down
35 changes: 32 additions & 3 deletions uzumibi-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ fn create_project(
println!("Creating project '{}'...", project_name);

// Collect feature overlay paths to know which files to skip from base
let feature_files = collect_feature_overlay_files(template, features);
let mut feature_files = collect_feature_overlay_files(template, features);

// When queue feature is active, skip app.rb (consumer.rb replaces it)
if features.iter().any(|f| f == "queue") {
feature_files.insert("lib/app.rb".to_string());
}

// Copy base template files (skip files that will be overridden by feature overlays)
copy_dir_recursive(
Expand Down Expand Up @@ -338,14 +343,17 @@ fn show_diff(existing_file: &Path, new_content: &[u8]) -> Result<(), Box<dyn std

fn substitute_project_name(content: &str, project_name: &str) -> String {
let project_name_underscore = project_name.replace('-', "_");
let project_name_kebab = project_name.replace('_', "-");

content
.replace("$$PROJECT_NAME$$", project_name)
.replace("$$PROJECT_NAME_UNDERSCORE$$", &project_name_underscore)
.replace("$$PROJECT_NAME_KEBAB$$", &project_name_kebab)
}

fn print_project_next_steps(template: &str, project_name: &str, features: &[String]) {
let has_enable_external = features.iter().any(|f| f == "enable-external");
let has_queue = features.iter().any(|f| f == "queue");

println!("\nNext steps:");
match template {
Expand All @@ -358,7 +366,7 @@ fn print_project_next_steps(template: &str, project_name: &str, features: &[Stri
println!(" \x1b[36mrustup target add wasm32-unknown-unknown\x1b[0m");
println!(" • Node.js tools:");
println!(" \x1b[36mnpm install -g pnpm wrangler\x1b[0m");
if has_enable_external {
if has_enable_external || has_queue {
println!(" • wasm-opt (Binaryen, required for asyncify):");
println!(" \x1b[36mbrew install binaryen\x1b[0m");
println!(" Or visit: https://github.com/WebAssembly/binaryen/releases");
Expand All @@ -370,7 +378,28 @@ fn print_project_next_steps(template: &str, project_name: &str, features: &[Stri
println!(" \x1b[36mpnpm run dev\x1b[0m");
println!(" 3. Deploy to Cloudflare:");
println!(" \x1b[36mpnpm run deploy\x1b[0m");
if has_enable_external {
if has_queue {
println!();
println!(
" \x1b[33mNote:\x1b[0m This project uses queue feature (Cloudflare Queues consumer)."
);
println!(
" Edit \x1b[36mlib/consumer.rb\x1b[0m to implement your queue consumer logic."
);
println!(" The following Uzumibi APIs are available in Ruby:");
println!(
" • \x1b[36mUzumibi::Message#ack!\x1b[0m / \x1b[36m#retry(delay_seconds: N)\x1b[0m → Message control"
);
println!(
" • \x1b[36mUzumibi::Fetch.fetch(url, method, body)\x1b[0m → Uzumibi::Response"
);
println!(
" • \x1b[36mUzumibi::KV.get(key)\x1b[0m / \x1b[36mUzumibi::KV.set(key, value)\x1b[0m → Durable Object storage"
);
println!(
" • \x1b[36mUzumibi::Queue.send(queue_name, message)\x1b[0m → Cloudflare Queue"
);
} else if has_enable_external {
println!();
println!(" \x1b[33mNote:\x1b[0m This project uses enable-external feature.");
println!(" The following Uzumibi APIs are available in Ruby:");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
"observability": {
"enabled": true
},
/**
* Queues
* Used for Uzumibi::Queue.send
* Uncomment the following block to enable queue producer capabilities.
* And create queue via command wrangler queue create $$PROJECT_NAME_KEBAB$$-queue
* https://developers.cloudflare.com/queues/
*/
// "queues": {
// "producers": [
// {
// "binding": "UZUMIBI_QUEUE",
// "queue": "$$PROJECT_NAME_KEBAB$$-queue"
// }
// ]
// },
/**
* Durable Objects
* Used for Uzumibi::KV.get/set
Expand All @@ -31,17 +46,4 @@
]
}
]
/**
* Queues
* Used for Uzumibi::Queue.send
* https://developers.cloudflare.com/queues/
*/
// "queues": {
// "producers": [
// {
// "binding": "MY_QUEUE",
// "queue": "my-queue-name"
// }
// ]
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Consumer < Uzumibi::Consumer
# @rbs message: Uzumibi::Message
def on_receive(message)
debug_console("[Consumer] Received message: id=#{message.id}, body=#{message.body}, attempts=#{message.attempts}")

if message.attempts > 3
debug_console("[Consumer] Acknowledging message #{message.id} after 3 attempts!!")
message.ack!
else
message.retry(delay_seconds: 3)
end
end
end

$CONSUMER = Consumer.new
18 changes: 18 additions & 0 deletions uzumibi-cli/templates/cloudflare/__features__/queue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "$$PROJECT_NAME$$",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "npm run build:wasm:asyncify && wrangler deploy",
"dev": "npm run build:wasm:asyncify && wrangler dev",
"build:wasm:asyncify": "cargo build --package $$PROJECT_NAME$$ --target wasm32-unknown-unknown --release --features queue && wasm-opt --enable-bulk-memory --enable-nontrapping-float-to-int --asyncify -O2 target/wasm32-unknown-unknown/release/$$PROJECT_NAME_UNDERSCORE$$.wasm -o ./src/$$PROJECT_NAME_UNDERSCORE$$_queue.wasm",
"start": "wrangler dev",
"test": "vitest"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.19",
"asyncify-wasm": "^1.2.0",
"vitest": "~3.2.0",
"wrangler": "^4.54.0"
}
}
Loading