Skip to content

Commit ac196ec

Browse files
CopilotTheaxiom
andauthored
Merge remote-tracking branch 'origin/main' into feat-dev-server-hmr-17194680974801638956
# Conflicts: # cli/src/commands/dev.rs # foundry/client/src/migrate/analyzer.rs # runtime/Cargo.toml # runtime/src/dev/dev_server.rs # runtime/src/dev/hot_reload.rs Co-authored-by: Theaxiom <57013+Theaxiom@users.noreply.github.com>
2 parents bbdb352 + e3d67bb commit ac196ec

46 files changed

Lines changed: 1588 additions & 170 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/security.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ jobs:
2323
# sqlx's proc-macro crate pulls unconditionally regardless of features.
2424
# Forge never performs RSA operations; rsa is never invoked at runtime.
2525
# Revisit when sqlx gates sqlx-mysql behind a feature flag.
26-
ignore: RUSTSEC-2023-0071
26+
#
27+
# RUSTSEC-2026-0097: rand unsoundness via custom logger + rand::rng().
28+
# No patched version exists for rand 0.8.x or 0.9.x. rand is a purely
29+
# transitive dependency; Forge defines no custom logger that accesses
30+
# rand::rng(), so the unsoundness preconditions are not met.
31+
# Remove when a patched rand version is released.
32+
ignore: RUSTSEC-2023-0071,RUSTSEC-2026-0097
2733

2834
codeql:
2935
name: CodeQL

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ rust_guardian.*
4141
# Test artifacts
4242
*.profraw
4343
lcov.info
44+
update_analyzer.sh

Cargo.lock

Lines changed: 60 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ tracing = "0.1"
4444
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
4545

4646
# CLI
47-
clap = { version = "4", features = ["derive", "cargo", "color", "unicode"] }
47+
clap = { version = "4", features = ["derive", "cargo", "color", "unicode", "env"] }
4848

4949
# Cryptography
5050
sha2 = "0.10"

cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ anyhow = { workspace = true }
2626
tracing = { workspace = true }
2727
tracing-subscriber = { workspace = true }
2828
camino = { workspace = true }
29+
notify.workspace = true

cli/src/commands/build.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! `forge build` — Compile the project for deployment targets.
22
3-
use anyhow::Result;
3+
use anyhow::{Context, Result};
44
use clap::Args;
5+
use std::env;
56

67
#[derive(Debug, Args)]
78
pub struct BuildArgs {
@@ -13,7 +14,56 @@ pub struct BuildArgs {
1314
pub minify: bool,
1415
}
1516

16-
pub async fn run(_args: BuildArgs) -> Result<()> {
17-
// TODO: Delegate to forge-compiler
17+
pub async fn run(args: BuildArgs) -> Result<()> {
18+
let current_dir = camino::Utf8PathBuf::try_from(env::current_dir()?)?;
19+
let manifest_path = current_dir.join("forge.toml");
20+
21+
crate::output::info(&format!("Reading manifest at {}", manifest_path));
22+
23+
let manifest = forge_compiler::parser::forge_toml::parse_forge_toml(&manifest_path)
24+
.context("Failed to read or parse forge.toml")?;
25+
26+
let targets_to_build: Vec<_> = if let Some(target_name) = &args.target {
27+
manifest
28+
.target
29+
.into_iter()
30+
.filter(|t| t.name == *target_name)
31+
.collect()
32+
} else {
33+
manifest.target
34+
};
35+
36+
if targets_to_build.is_empty() {
37+
crate::output::warn("No targets found to build.");
38+
return Ok(());
39+
}
40+
41+
for target in targets_to_build {
42+
crate::output::info(&format!("Building target '{}'", target.name));
43+
44+
let options = forge_compiler::CompileOptions {
45+
target: target.target_type.clone(),
46+
source_maps: manifest.build.source_maps.unwrap_or(true),
47+
minify: args.minify,
48+
project_root: current_dir.clone(),
49+
};
50+
51+
match forge_compiler::compile(options) {
52+
Ok(output) => {
53+
crate::output::success(&format!(
54+
"Successfully built target '{}' ({} assets)",
55+
target.name,
56+
output.assets.len() + 1
57+
));
58+
}
59+
Err(e) => {
60+
crate::output::error(&format!("Failed to build target '{}': {}", target.name, e));
61+
return Err(e.into());
62+
}
63+
}
64+
}
65+
66+
crate::output::success("Build completed successfully");
67+
1868
Ok(())
1969
}

cli/src/commands/init.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,21 @@ pub struct InitArgs {
99
pub directory: Option<camino::Utf8PathBuf>,
1010
}
1111

12-
pub async fn run(_args: InitArgs) -> Result<()> {
13-
// TODO: Add forge.toml to existing project
12+
pub async fn run(args: InitArgs) -> Result<()> {
13+
use forge_shared::init::{init_project, InitOptions};
14+
15+
let dir = args
16+
.directory
17+
.unwrap_or_else(|| camino::Utf8PathBuf::from("."));
18+
19+
let options = InitOptions { target_dir: dir };
20+
21+
let (initialized_dir, name) = init_project(options)?;
22+
23+
crate::output::success(&format!(
24+
"Initialized Forge project '{}' in {}",
25+
name, initialized_dir
26+
));
27+
1428
Ok(())
1529
}

cli/src/commands/install.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub struct InstallArgs {
1212
pub dev: bool,
1313
}
1414

15-
pub async fn run(_args: InstallArgs) -> Result<()> {
16-
// TODO: Delegate to foundry-client resolver
15+
pub async fn run(args: InstallArgs) -> Result<()> {
16+
foundry_client::resolver::install_packages(args.packages, args.dev).await?;
1717
Ok(())
1818
}

cli/src/commands/new.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
//! - Database adapter (SQLite default, PostgreSQL)
1414
//! - Authentication (email+password, passkey, OAuth)
1515
16-
use anyhow::Result;
16+
use anyhow::{Context, Result};
1717
use clap::Args;
18+
use std::fs;
19+
use std::path::PathBuf;
1820

1921
#[derive(Debug, Args)]
2022
pub struct NewArgs {
@@ -27,7 +29,71 @@ pub struct NewArgs {
2729

2830
pub async fn run(args: NewArgs) -> Result<()> {
2931
crate::output::info(&format!("Creating new Forge project: {}", args.name));
30-
// TODO: Implement project scaffolding
32+
33+
let base_path = PathBuf::from(&args.name);
34+
35+
// Create directory structure
36+
fs::create_dir_all(base_path.join("app").join("pages"))
37+
.with_context(|| format!("Failed to create project directories in {}", args.name))?;
38+
39+
// Write forge.toml
40+
let forge_toml_content = format!(
41+
r#"[project]
42+
name = "{}"
43+
version = "0.1.0"
44+
45+
[build]
46+
entry = "app/root.fx"
47+
output = ".forge/dist"
48+
"#,
49+
args.name
50+
);
51+
fs::write(base_path.join("forge.toml"), forge_toml_content)?;
52+
53+
// Write app/root.fx
54+
let root_fx_content = r#"export default function Root({ children }) {
55+
return (
56+
<html>
57+
<head>
58+
<title>New Forge App</title>
59+
</head>
60+
<body>
61+
{children}
62+
</body>
63+
</html>
64+
);
65+
}
66+
"#;
67+
fs::write(base_path.join("app").join("root.fx"), root_fx_content)?;
68+
69+
// Write app/routes.fx
70+
let routes_fx_content = r#"import Home from './pages/Home.fx';
71+
72+
export default [
73+
{ path: '/', component: Home },
74+
];
75+
"#;
76+
fs::write(base_path.join("app").join("routes.fx"), routes_fx_content)?;
77+
78+
// Write app/pages/Home.fx
79+
let home_fx_content = r#"export default function Home() {
80+
return <h1>Welcome to Forge!</h1>;
81+
}
82+
"#;
83+
fs::write(
84+
base_path.join("app").join("pages").join("Home.fx"),
85+
home_fx_content,
86+
)?;
87+
88+
// Write schema.fx
89+
fs::write(base_path.join("schema.fx"), "// Empty database schema\n")?;
90+
91+
// Write foundry.lock
92+
fs::write(
93+
base_path.join("foundry.lock"),
94+
"# foundry.lock — generated by forge, do not edit manually\n",
95+
)?;
96+
3197
crate::output::success(&format!(
3298
"Created {} — run `cd {} && forge dev` to start",
3399
args.name, args.name

cli/src/commands/publish.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! `forge publish` — Publish a package to the Foundry registry.
22
3+
use crate::config::CliConfig;
34
use anyhow::Result;
45
use clap::Args;
56

@@ -10,7 +11,20 @@ pub struct PublishArgs {
1011
pub dry_run: bool,
1112
}
1213

13-
pub async fn run(_args: PublishArgs) -> Result<()> {
14-
// TODO: Delegate to foundry-client publish
14+
pub async fn run(args: PublishArgs) -> Result<()> {
15+
let config = CliConfig::load();
16+
let registry_url = config
17+
.registry_url
18+
.unwrap_or_else(|| "https://registry.forgejs.com".to_string());
19+
20+
let options = foundry_client::publish::PublishOptions {
21+
dir: camino::Utf8PathBuf::from("."),
22+
dry_run: args.dry_run,
23+
registry_url,
24+
auth_token: config.auth_token,
25+
};
26+
27+
foundry_client::publish::publish_package(options).await?;
28+
1529
Ok(())
1630
}

0 commit comments

Comments
 (0)