Skip to content

Commit 5aeeb0c

Browse files
committed
feat: install bun if not present
1 parent 2cd0131 commit 5aeeb0c

File tree

6 files changed

+107
-11
lines changed

6 files changed

+107
-11
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
3+
use crate::{
4+
cli::{self, traits::Confirm},
5+
common::binary::SemanticVersion,
6+
};
7+
use anyhow::{Result, anyhow};
8+
use duct::cmd;
9+
use std::path::PathBuf;
10+
11+
/// Ensure Bun exists and return the absolute path to the `bun` binary.
12+
///
13+
/// # Arguments
14+
/// * `cli`: Command line interface.
15+
pub fn ensure_bun(cli: &mut impl cli::traits::Cli) -> Result<PathBuf> {
16+
if let Some(path) = which("bun") {
17+
return Ok(PathBuf::from(path));
18+
}
19+
if !cli
20+
.confirm("📦 Do you want to proceed with the installation of the following package: bun ?")
21+
.initial_value(true)
22+
.interact()?
23+
{
24+
return Err(anyhow::anyhow!("🚫 You have cancelled the installation process."));
25+
}
26+
// Install Bun (macOS/Linux official script)
27+
cmd("bash", vec!["-lc", r#"curl -fsSL https://bun.sh/install | bash"#]).run()?;
28+
// Use the default install location from the official installer
29+
let home = std::env::var("HOME").map_err(|_| anyhow!("HOME not set"))?;
30+
let bun_abs = PathBuf::from(format!("{home}/.bun/bin/bun"));
31+
32+
if !bun_abs.exists() {
33+
return Err(anyhow!(format!(
34+
"Bun installed but not found at {}. Open a new shell or add it to PATH.",
35+
bun_abs.display()
36+
)));
37+
}
38+
Ok(bun_abs)
39+
}
40+
41+
/// Require Node v20+ to be installed.
42+
pub fn ensure_node_v20() -> Result<()> {
43+
let v = SemanticVersion::try_from("node".to_string()).map_err(|_| {
44+
anyhow!("NodeJS v20+ required but not found. Install from https://nodejs.org and re-run.")
45+
})?;
46+
if v.0 < 20 {
47+
return Err(anyhow!(format!(
48+
"NodeJS v20+ required. Detected {}.{}.{}. Please upgrade Node and re-run.",
49+
v.0, v.1, v.2
50+
)));
51+
}
52+
Ok(())
53+
}
54+
55+
/// Require `npx` to be available.
56+
pub fn ensure_npx() -> Result<()> {
57+
if !has("npx") && !has("npm") {
58+
return Err(anyhow!(
59+
"`npx` (or npm with npx) not found on PATH. Install NodeJS from https://nodejs.org and re-run."
60+
));
61+
}
62+
Ok(())
63+
}
64+
65+
fn has(bin: &str) -> bool {
66+
cmd("which", vec![bin]).read().is_ok()
67+
}
68+
69+
fn which(bin: &str) -> Option<String> {
70+
cmd("which", vec![bin]).read().ok().map(|s| s.trim().to_string())
71+
}

crates/pop-cli/src/commands/install/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ use os_info::Type;
1515
use strum_macros::Display;
1616
use tokio::fs;
1717

18+
/// Utilities for installing the needed libraries for frontend development.
19+
pub mod frontend;
20+
1821
const DOCS_URL: &str = "https://docs.polkadot.com/develop/parachains/install-polkadot-sdk/";
1922

2023
#[derive(Display)]

crates/pop-cli/src/commands/new/chain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ async fn generate_parachain_from_template(
259259
))
260260
}
261261
if let Some(frontend_template) = &frontend_template {
262-
create_frontend(&destination_path, frontend_template)?;
262+
create_frontend(&destination_path, frontend_template, cli)?;
263263
next_steps.push(format!(
264264
"Frontend template {frontend_template} created inside \"{name_template}\". Go to the folder and follow the README instructions to get started."
265265
))

crates/pop-cli/src/commands/new/contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ fn generate_contract_from_template(
200200
next_steps.push("Use `pop up contract` to deploy your contract to a live network.".to_string());
201201

202202
if let Some(frontend_template) = &frontend_template {
203-
create_frontend(contract_path.as_path(), frontend_template)?;
203+
create_frontend(contract_path.as_path(), frontend_template, cli)?;
204204
next_steps.push(format!(
205205
"Frontend template {frontend_template} created inside {:?}. Go to the folder and follow the README instructions to get started.", contract_path.display()
206206
))

crates/pop-cli/src/commands/new/frontend.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
use std::path::Path;
44

5-
use crate::cli::{self, traits::*};
5+
use crate::{
6+
cli::{self, traits::*},
7+
install::frontend::{ensure_bun, ensure_node_v20, ensure_npx},
8+
};
69
use anyhow::Result;
710
use duct::cmd;
811
use pop_common::{
@@ -35,13 +38,32 @@ pub fn prompt_frontend_template(
3538
/// # Arguments
3639
/// * `target` - Location where the smart contract will be created.
3740
/// * `template` - Frontend template to generate the contract from.
38-
pub fn create_frontend(target: &Path, template: &FrontendTemplate) -> Result<()> {
39-
let command = template
40-
.command()
41-
.ok_or_else(|| anyhow::anyhow!("no command configured for {:?}", template))?;
42-
// TODO: Ensure `node` and `npx` are available in PATH (Or check for others bun...)
43-
// If error exist the proces and show an error to the user, or install?
44-
cmd("npx", vec![command, "frontend"]).dir(target.canonicalize()?).run()?;
41+
/// * `cli`: Command line interface.
42+
pub fn create_frontend(
43+
target: &Path,
44+
template: &FrontendTemplate,
45+
cli: &mut impl cli::traits::Cli,
46+
) -> Result<()> {
47+
ensure_node_v20()?;
48+
ensure_npx()?;
49+
let project_dir = target.canonicalize()?;
50+
match template {
51+
// Inkathon requires Bun installed.
52+
FrontendTemplate::Inkathon => {
53+
let bun = ensure_bun(cli)?;
54+
cmd(&bun, &["x", "create-inkathon-app@latest", "frontend"])
55+
.dir(&project_dir)
56+
.env("SKIP_INSTALL_SIMPLE_GIT_HOOKS", "1")
57+
.unchecked()
58+
.run()?;
59+
},
60+
_ => {
61+
let command = template
62+
.command()
63+
.ok_or_else(|| anyhow::anyhow!("no command configured for {:?}", template))?;
64+
cmd("npx", vec![command, "frontend"]).dir(&project_dir).run()?;
65+
},
66+
}
4567
Ok(())
4668
}
4769

crates/pop-cli/src/commands/new/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::fmt::{Display, Formatter, Result};
99
pub mod chain;
1010
#[cfg(any(feature = "polkavm-contracts", feature = "wasm-contracts"))]
1111
pub mod contract;
12-
/// Utilities for selecting a frontendtemplate and generate it.
12+
/// Utilities for selecting a frontend template and generate it.
1313
pub mod frontend;
1414
#[cfg(feature = "chain")]
1515
pub mod pallet;

0 commit comments

Comments
 (0)