From f4ad756f77499f9d61ad9240efb704436c4c907f Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 17:28:48 -0400 Subject: [PATCH 1/6] refactor: add npm build step --- src/commands/build.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/commands/build.rs b/src/commands/build.rs index 5bd63cc..d21bf2f 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,5 +1,6 @@ use crate::{ cli::{BuildSystem, ResolvedArgs}, + commands::read_package_json, ctx::RunCtx, }; use std::path::Path; @@ -103,13 +104,19 @@ pub fn npm_build( writeln!(ctx.io.output, "Installing dependencies").ok(); run_timed(&["npm", "install"], Some(source_path), "npm install", ctx)?; if !args.build.no_build && !is_mono { - writeln!(ctx.io.output, "Building project").ok(); - run_timed( - &["npm", "run", "build"], - Some(source_path), - "npm build", - ctx, - )?; + let has_build = read_package_json(source_path, "skipping build", &mut ctx.io, ctx.flags) + .is_some_and(|j| j.get("scripts").and_then(|s| s.get("build")).is_some()); + if has_build { + writeln!(ctx.io.output, "Building project").ok(); + run_timed( + &["npm", "run", "build"], + Some(source_path), + "npm build", + ctx, + )?; + } else if ctx.flags.verbose { + writeln!(ctx.io.output, " No build script found, skipping build").ok(); + } } Ok(()) } From 4e38a8f61ec44a68e43e8868fa6984cb296a8962 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 17:31:00 -0400 Subject: [PATCH 2/6] feat: fall back to vercel dev for endpoint projects without a dev script --- src/commands/dev.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/commands/dev.rs b/src/commands/dev.rs index e02de5d..b801d41 100644 --- a/src/commands/dev.rs +++ b/src/commands/dev.rs @@ -8,6 +8,7 @@ use crate::{ commands::read_package_json, ctx::{IoCtx, RunCtx, RunFlags}, }; +use serde_json::Value; use std::{ path::Path, process::{Command, Stdio}, @@ -21,13 +22,23 @@ pub fn resolve_dev_command( ) -> Option { let json = read_package_json(repo_path, "skipping dev server", io, flags)?; if json.get("scripts").and_then(|s| s.get("dev")).is_some() { - Some("npm run dev".to_string()) - } else { - if flags.verbose { - writeln!(io.output, " No dev script found, skipping dev server").ok(); - } - None + return Some("npm run dev".to_string()); + } + if is_vercel_project(repo_path, &json) { + return Some("vercel dev".to_string()); } + if flags.verbose { + writeln!(io.output, " No dev script found, skipping dev server").ok(); + } + None +} + +fn is_vercel_project(repo_path: &Path, json: &Value) -> bool { + repo_path.join("vercel.json").exists() + || repo_path.join("api").is_dir() + || ["dependencies", "devDependencies"] + .iter() + .any(|k| json.get(k).and_then(|d| d.get("@vercel/node")).is_some()) } /// Runs the dev server in the foreground, blocking until it exits (Ctrl-C). From 7147c3b8412e842896dcf0de3d4134e9f818990d Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 17:43:55 -0400 Subject: [PATCH 3/6] test: cover npm build skip when no build script is present --- tests/commands/build.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/commands/build.rs b/tests/commands/build.rs index 16f4eef..8d292bb 100644 --- a/tests/commands/build.rs +++ b/tests/commands/build.rs @@ -3,6 +3,7 @@ use star_setup::{ cli::BuildSystem, commands::{build_project, cmake_build, meson_build, npm_build}, }; +use std::fs::write; /* ===== BUILD_PROJECT ===== */ #[test] @@ -99,6 +100,11 @@ fn test_npm_build_install_only() { fn test_npm_build_with_build_step() { let args = default_resolved_with_no_build(false); let runner = with_ctx_runner(MockRunner::new(), |tmp_path, ctx| { + write( + tmp_path.join("package.json"), + r#"{"scripts": {"build": "tsc"}}"#, + ) + .unwrap(); npm_build(&args, tmp_path, false, ctx).unwrap(); }); assert_eq!(runner.calls.len(), 2); @@ -114,3 +120,22 @@ fn test_npm_build_install_only_prints_header() { let out = String::from_utf8(output).unwrap(); assert!(out.contains("Installing dependencies")); } + +#[test] +fn test_npm_build_skips_build_without_script() { + let args = default_resolved_with_no_build(false); + let runner = with_ctx_runner(MockRunner::new(), |tmp_path, ctx| { + write(tmp_path.join("package.json"), r#"{"scripts": {"typecheck": "tsc --noEmit"}}"#).unwrap(); + npm_build(&args, tmp_path, false, ctx).unwrap(); + }); + assert_eq!(runner.calls.len(), 1); +} + +#[test] +fn test_npm_build_skips_build_when_package_json_missing() { + let args = default_resolved_with_no_build(false); + let runner = with_ctx_runner(MockRunner::new(), |tmp_path, ctx| { + npm_build(&args, tmp_path, false, ctx).unwrap(); + }); + assert_eq!(runner.calls.len(), 1); +} From 4217730b04e1d2b99c9907ae70d7d8b36025b80a Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 17:56:26 -0400 Subject: [PATCH 4/6] test: cover resolve_dev_command's vercel dev fallback --- tests/commands.rs | 2 ++ tests/commands/build.rs | 6 +++- tests/commands/dev.rs | 79 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/commands/dev.rs diff --git a/tests/commands.rs b/tests/commands.rs index 9aa7a0e..a6d3359 100644 --- a/tests/commands.rs +++ b/tests/commands.rs @@ -3,6 +3,8 @@ mod common; #[path = "commands/build.rs"] mod build; +#[path = "commands/dev.rs"] +mod dev; #[path = "commands/display.rs"] mod display; #[path = "commands/header.rs"] diff --git a/tests/commands/build.rs b/tests/commands/build.rs index 8d292bb..dd012cc 100644 --- a/tests/commands/build.rs +++ b/tests/commands/build.rs @@ -125,7 +125,11 @@ fn test_npm_build_install_only_prints_header() { fn test_npm_build_skips_build_without_script() { let args = default_resolved_with_no_build(false); let runner = with_ctx_runner(MockRunner::new(), |tmp_path, ctx| { - write(tmp_path.join("package.json"), r#"{"scripts": {"typecheck": "tsc --noEmit"}}"#).unwrap(); + write( + tmp_path.join("package.json"), + r#"{"scripts": {"typecheck": "tsc --noEmit"}}"#, + ) + .unwrap(); npm_build(&args, tmp_path, false, ctx).unwrap(); }); assert_eq!(runner.calls.len(), 1); diff --git a/tests/commands/dev.rs b/tests/commands/dev.rs new file mode 100644 index 0000000..5eac89b --- /dev/null +++ b/tests/commands/dev.rs @@ -0,0 +1,79 @@ +use crate::common::with_io_output; +use star_setup::{commands::resolve_dev_command, ctx::RunFlags}; +use std::fs::{create_dir_all, write}; +use tempfile::TempDir; + +fn flags(verbose: bool) -> RunFlags { + RunFlags { + verbose, + timing: false, + dry_run: false, + } +} + +#[test] +fn test_resolve_dev_command_uses_dev_script() { + let tmp = TempDir::new().unwrap(); + write( + tmp.path().join("package.json"), + r#"{"scripts": {"dev": "vite"}}"#, + ) + .unwrap(); + let (result, _) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(false))); + assert_eq!(result, Some("npm run dev".to_string())); +} + +#[test] +fn test_resolve_dev_command_falls_back_to_vercel_dev_with_vercel_json() { + let tmp = TempDir::new().unwrap(); + write(tmp.path().join("package.json"), r#"{"scripts": {}}"#).unwrap(); + write(tmp.path().join("vercel.json"), "{}").unwrap(); + let (result, _) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(false))); + assert_eq!(result, Some("vercel dev".to_string())); +} + +#[test] +fn test_resolve_dev_command_falls_back_to_vercel_dev_with_api_dir() { + let tmp = TempDir::new().unwrap(); + write(tmp.path().join("package.json"), r#"{"scripts": {}}"#).unwrap(); + create_dir_all(tmp.path().join("api")).unwrap(); + let (result, _) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(false))); + assert_eq!(result, Some("vercel dev".to_string())); +} + +#[test] +fn test_resolve_dev_command_falls_back_to_vercel_dev_with_vercel_node_dependency() { + let tmp = TempDir::new().unwrap(); + write( + tmp.path().join("package.json"), + r#"{"scripts": {}, "dependencies": {"@vercel/node": "^5.0.0"}}"#, + ) + .unwrap(); + let (result, _) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(false))); + assert_eq!(result, Some("vercel dev".to_string())); +} + +#[test] +fn test_resolve_dev_command_falls_back_to_vercel_dev_with_vercel_node_dev_dependency() { + let tmp = TempDir::new().unwrap(); + write( + tmp.path().join("package.json"), + r#"{"scripts": {}, "devDependencies": {"@vercel/node": "^5.0.0"}}"#, + ) + .unwrap(); + let (result, _) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(false))); + assert_eq!(result, Some("vercel dev".to_string())); +} + +#[test] +fn test_resolve_dev_command_none_when_no_signals() { + let tmp = TempDir::new().unwrap(); + write( + tmp.path().join("package.json"), + r#"{"scripts": {"typecheck": "tsc"}}"#, + ) + .unwrap(); + let (result, out) = with_io_output(|io| resolve_dev_command(tmp.path(), io, flags(true))); + assert!(result.is_none()); + assert!(out.contains("No dev script found")); +} From cab8e494d2f35926150e53cdde9f6d99ac40fef3 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 17:57:59 -0400 Subject: [PATCH 5/6] chore: bump v0.4.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 17d7bc5..5528cb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "star-setup" -version = "0.4.6" +version = "0.4.7" edition = "2021" repository = "https://github.com/star-setup/core" description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems" From de52dd8c3db616fd2c52b1ce5d73ba0bf7c9fae3 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 18:00:30 -0400 Subject: [PATCH 6/6] docs: document endpoint build-skip and vercel dev fallback --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc83620..187ac0d 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,8 @@ Interactive mode complete ### Single Repository Mode Build system is auto-detected from the repository root (`CMakeLists.txt` → CMake, `meson.build` → Meson, `package.json` → npm). Pass `--dev` to launch the project's dev server after setup. +Endpoint-style npm projects (no `build` script) skip the build step. If there's also no `dev` script, `--dev` falls back to `vercel dev` when the repo looks like a Vercel project (`vercel.json`, an `api/` directory, or `@vercel/node` as a dependency). + ```bash # Clone and build using a single repository star-setup username/repo @@ -220,10 +222,11 @@ star-setup username/repo --repos user/lib1 user/lib2 star-setup username/repo --repos user/lib1 user/lib2 --no-watch ``` -After setup pass `--dev` to launch it automatically, or run the game from its repo directory: +After setup pass `--dev` to launch it automatically: falls back to `vercel dev` if the test repo has no `dev` script but looks like a Vercel project. Or, run it manually from its repo directory: ```bash cd build-mono/repos/user-my-repo -npm run dev +# npm run dev +# vercel dev ```