fix(xtask): polish generated app next steps
Give app new users app-local next steps and generated README/CREATED docs, rename visible v0 text, and fail early when the required hemplate checkout is missing instead of producing a broken path dependency. req: ceremony/005
This commit is contained in:
@@ -10,6 +10,11 @@ SaaS tutorial app, an advanced Kanban milestone sketch, and a full techdemo.
|
|||||||
The v1 story now has tutorial, recipe, diagnostics, and stability docs; see
|
The v1 story now has tutorial, recipe, diagnostics, and stability docs; see
|
||||||
`docs/v1-readiness.md` for the remaining close-gap audit.
|
`docs/v1-readiness.md` for the remaining close-gap audit.
|
||||||
|
|
||||||
|
Local checkout note: until the hemplate crates are published, this repository
|
||||||
|
expects `hemplate` checked out next to `hemx` as `../hemplate/hemplate`. The app
|
||||||
|
scaffolder fails with that exact path if the prerequisite is missing, instead of
|
||||||
|
creating an app that fails later with a vague Cargo path-dependency error.
|
||||||
|
|
||||||
## The normal path
|
## The normal path
|
||||||
|
|
||||||
For beginner and production-shaped app code, stay on this path. req: public_api/001 req: public_api/005
|
For beginner and production-shaped app code, stay on this path. req: public_api/001 req: public_api/005
|
||||||
|
|||||||
+40
-11
@@ -85,15 +85,16 @@ fn run_app_new(operands: &[String]) -> ExitCode {
|
|||||||
if mobile { "app-new-mobile" } else { "app-new" },
|
if mobile { "app-new-mobile" } else { "app-new" },
|
||||||
destination.display()
|
destination.display()
|
||||||
);
|
);
|
||||||
println!(
|
println!("next\tcd {}", destination.display());
|
||||||
"next\tcargo test --manifest-path {}/Cargo.toml",
|
|
||||||
destination.display()
|
|
||||||
);
|
|
||||||
if mobile {
|
if mobile {
|
||||||
println!(
|
println!("next\t./hemx-app test");
|
||||||
"next\tHEMX_APP_ORIGIN=https://app.example.com {}/hemx-app mobile-release",
|
println!("next\t./hemx-app build");
|
||||||
destination.display()
|
println!("next\tHEMX_APP_ORIGIN=https://app.example.com ./hemx-app mobile-release");
|
||||||
);
|
println!("next\tHEMX_APP_ORIGIN=https://app.example.com ./hemx-app mobile-verify");
|
||||||
|
} else {
|
||||||
|
println!("next\tcargo test");
|
||||||
|
println!("next\tcargo run");
|
||||||
|
println!("next\tcargo build --release");
|
||||||
}
|
}
|
||||||
ExitCode::SUCCESS
|
ExitCode::SUCCESS
|
||||||
}
|
}
|
||||||
@@ -175,6 +176,7 @@ fn run_workout_new(destination: Option<&str>) -> ExitCode {
|
|||||||
|
|
||||||
fn create_app_scaffold(destination: &Path) -> std::io::Result<()> {
|
fn create_app_scaffold(destination: &Path) -> std::io::Result<()> {
|
||||||
let root = repo_root()?;
|
let root = repo_root()?;
|
||||||
|
let hemplate = hemplate_checkout(&root)?;
|
||||||
copy_dir(&root.join("examples/v0"), destination)?;
|
copy_dir(&root.join("examples/v0"), destination)?;
|
||||||
let cargo_toml = destination.join("Cargo.toml");
|
let cargo_toml = destination.join("Cargo.toml");
|
||||||
let manifest = fs::read_to_string(&cargo_toml)?;
|
let manifest = fs::read_to_string(&cargo_toml)?;
|
||||||
@@ -186,7 +188,7 @@ fn create_app_scaffold(destination: &Path) -> std::io::Result<()> {
|
|||||||
.replace("edition.workspace = true", "edition = \"2021\"")
|
.replace("edition.workspace = true", "edition = \"2021\"")
|
||||||
.replace(
|
.replace(
|
||||||
"path = \"../../../hemplate/hemplate\"",
|
"path = \"../../../hemplate/hemplate\"",
|
||||||
&format!("path = \"{}\"", root.join("../hemplate/hemplate").display()),
|
&format!("path = \"{}\"", hemplate.display()),
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"path = \"../../hemx\"",
|
"path = \"../../hemx\"",
|
||||||
@@ -207,9 +209,14 @@ fn create_app_scaffold(destination: &Path) -> std::io::Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
replace_in_tree(destination, "hemx_v0_examples", "hemx_app")?;
|
replace_in_tree(destination, "hemx_v0_examples", "hemx_app")?;
|
||||||
replace_in_tree(destination, "hemx-v0-examples", "hemx-app")?;
|
replace_in_tree(destination, "hemx-v0-examples", "hemx-app")?;
|
||||||
|
replace_in_tree(destination, "hemx v0 browser examples", "hemx app")?;
|
||||||
|
fs::write(
|
||||||
|
destination.join("README.md"),
|
||||||
|
"# hemx app\n\nThis app was created by `cargo run -p hemx-xtask -- app new PATH`. It is the generic checked-hypermedia starter: one page, form, keyed row partial, notice slot, Rust handlers, and tests using generated helpers instead of raw ids, opcodes, selector UI JavaScript, or manual registry plumbing. req: ceremony/005\n\n## Run\n\n```sh\ncargo run\n```\n\nOpen <http://127.0.0.1:3000>.\n\n## Test and build\n\n```sh\ncargo test\ncargo build --release\n```\n\n## Edit\n\n- `templates/todos.heml` owns the reusable partials and generated target names.\n- `src/main.rs` owns handlers and app state.\n- `src/lib.rs` re-exports generated `ui` helpers.\n\nThe reusable todo row partial is rendered in the initial page and updated through generated append/replace/remove/dynamic-batch effects. req: canonical_authoring/003\n",
|
||||||
|
)?;
|
||||||
fs::write(
|
fs::write(
|
||||||
destination.join("CREATED.md"),
|
destination.join("CREATED.md"),
|
||||||
"# Created hemx app\n\nThis scaffold is the generic checked-hypermedia starting point: one page, form, keyed row partial, notice slot, Rust handlers, and tests using generated helpers instead of raw ids, opcodes, selector UI JavaScript, or manual registry plumbing.\n\n```sh\ncargo test\ncargo run\n```\n\nThe reusable todo row partial is rendered in the initial page and updated through generated append/replace/remove/dynamic-batch effects. req: canonical_authoring/003 req: ceremony/005\n",
|
"# Created hemx app\n\nUse one app-owned command surface from this directory:\n\n```sh\ncargo test\ncargo run\ncargo build --release\n```\n\nThis scaffold is the generic checked-hypermedia starting point: one page, form, keyed row partial, notice slot, Rust handlers, and tests using generated helpers instead of raw ids, opcodes, selector UI JavaScript, or manual registry plumbing.\n\nThe reusable todo row partial is rendered in the initial page and updated through generated append/replace/remove/dynamic-batch effects. req: canonical_authoring/003 req: ceremony/005\n",
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -256,6 +263,7 @@ fn create_mobile_app_scaffold(destination: &Path) -> std::io::Result<()> {
|
|||||||
|
|
||||||
fn create_workout_app(destination: &Path) -> std::io::Result<()> {
|
fn create_workout_app(destination: &Path) -> std::io::Result<()> {
|
||||||
let root = repo_root()?;
|
let root = repo_root()?;
|
||||||
|
let hemplate = hemplate_checkout(&root)?;
|
||||||
copy_dir(&root.join("examples/workout"), destination)?;
|
copy_dir(&root.join("examples/workout"), destination)?;
|
||||||
let cargo_toml = destination.join("Cargo.toml");
|
let cargo_toml = destination.join("Cargo.toml");
|
||||||
let manifest = fs::read_to_string(&cargo_toml)?;
|
let manifest = fs::read_to_string(&cargo_toml)?;
|
||||||
@@ -274,7 +282,7 @@ fn create_workout_app(destination: &Path) -> std::io::Result<()> {
|
|||||||
.replace("edition.workspace = true", "edition = \"2021\"")
|
.replace("edition.workspace = true", "edition = \"2021\"")
|
||||||
.replace(
|
.replace(
|
||||||
"path = \"../../../hemplate/hemplate\"",
|
"path = \"../../../hemplate/hemplate\"",
|
||||||
&format!("path = \"{}\"", root.join("../hemplate/hemplate").display()),
|
&format!("path = \"{}\"", hemplate.display()),
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"path = \"../../hemx\"",
|
"path = \"../../hemx\"",
|
||||||
@@ -318,6 +326,21 @@ fn repo_root() -> std::io::Result<PathBuf> {
|
|||||||
.to_path_buf())
|
.to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hemplate_checkout(root: &Path) -> std::io::Result<PathBuf> {
|
||||||
|
let hemplate = root.join("../hemplate/hemplate");
|
||||||
|
if hemplate.join("Cargo.toml").exists() {
|
||||||
|
Ok(hemplate)
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
format!(
|
||||||
|
"hemplate checkout not found at {}; clone hemplate next to hemx or adjust the generated Cargo.toml after creation",
|
||||||
|
hemplate.display()
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn replace_in_file(path: &Path, from: &str, to: &str) -> std::io::Result<()> {
|
fn replace_in_file(path: &Path, from: &str, to: &str) -> std::io::Result<()> {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
fs::write(path, contents.replace(from, to))
|
fs::write(path, contents.replace(from, to))
|
||||||
@@ -1254,6 +1277,7 @@ mod tests {
|
|||||||
let main_rs = fs::read_to_string(destination.join("src/main.rs")).expect("main rs");
|
let main_rs = fs::read_to_string(destination.join("src/main.rs")).expect("main rs");
|
||||||
let template =
|
let template =
|
||||||
fs::read_to_string(destination.join("templates/todos.heml")).expect("template");
|
fs::read_to_string(destination.join("templates/todos.heml")).expect("template");
|
||||||
|
let readme = fs::read_to_string(destination.join("README.md")).expect("readme");
|
||||||
let created = fs::read_to_string(destination.join("CREATED.md")).expect("created docs");
|
let created = fs::read_to_string(destination.join("CREATED.md")).expect("created docs");
|
||||||
|
|
||||||
assert!(manifest.contains("name = \"hemx-app\""));
|
assert!(manifest.contains("name = \"hemx-app\""));
|
||||||
@@ -1265,6 +1289,11 @@ mod tests {
|
|||||||
assert!(template.contains("data-hemx-slot=\"notice\""));
|
assert!(template.contains("data-hemx-slot=\"notice\""));
|
||||||
assert!(template.contains("data-hemx-slot=\"todo_row\""));
|
assert!(template.contains("data-hemx-slot=\"todo_row\""));
|
||||||
assert!(template.contains("h-key=\"row.id\""));
|
assert!(template.contains("h-key=\"row.id\""));
|
||||||
|
assert!(readme.contains("# hemx app"));
|
||||||
|
assert!(readme.contains("cargo run"));
|
||||||
|
assert!(readme.contains("cargo build --release"));
|
||||||
|
assert!(readme.contains("templates/todos.heml"));
|
||||||
|
assert!(!readme.contains("cargo run -p hemx-app"));
|
||||||
assert!(created.contains("keyed row partial"));
|
assert!(created.contains("keyed row partial"));
|
||||||
assert!(created.contains("generated append/replace/remove/dynamic-batch effects"));
|
assert!(created.contains("generated append/replace/remove/dynamic-batch effects"));
|
||||||
assert!(destination.join("src/lib.rs").exists());
|
assert!(destination.join("src/lib.rs").exists());
|
||||||
|
|||||||
Reference in New Issue
Block a user