Skip to content

Add local workspace and site build/check support - #10

Closed
ritorhymes wants to merge 1 commit into
eips-wg:masterfrom
ritovision:local-build-system-01-foundation
Closed

Add local workspace and site build/check support#10
ritorhymes wants to merge 1 commit into
eips-wg:masterfrom
ritovision:local-build-system-01-foundation

Conversation

@ritorhymes

Copy link
Copy Markdown
Contributor

Important

This is the first PR in a stacked preprocessor series for preprocessor#8. That series splits the original implementation from preprocessor#9 into smaller reviewable PRs. This first slice introduces the local workspace and site build/check foundation. Later stacked PRs add the explicit editorial command surface, local serving and preview, and the public workflow documentation.

Description

This PR adds the first usable foundation for the local multi-repo build system in preprocessor.

It introduces the local workspace model, the workspace config/profile layer, and the site build/check path against sibling repo and theme inputs. The goal of this slice is to make the system locally usable without pulling in the later editorial and serving features yet.

Changes

  • add the local workspace config and profile model
  • add workspace init, workspace refresh, and workspace doctor
  • generate a local workspace justfile for common tasks
  • add the local site check and build paths
  • assemble sibling repo and theme inputs into disposable build-time repos
  • support active content repos checked out as either standard clones or git worktrees
  • add parity-oriented inputs and dirty build support for the site path

Not Yet In This PR

This PR does not yet add:

  • explicit editorial commands
  • local serve / preview
  • the full public workflow documentation

Those land in the later stacked PRs.

Validation

This foundation slice builds cleanly on its own:

  • cargo build

The broader runtime paths for the local build system were validated on the original combined implementation in preprocessor#9 before restacking, including clean and dirty site builds, parity-oriented checks, and downstream staging verification from the rewritten EIPs#10 and ERCs#10 branches.

This stacked series preserves the final tracked tree of the original local-build-system implementation from preprocessor#9 exactly.

Add the local workspace model for the multi-repo build system.

This adds workspace config and profile support, workspace init,
refresh, and doctor commands, generated helper tasks, and the
local site build path against sibling repos and theme inputs.

The site commands support normal local usage, parity checks,
standard clone and git worktree checkouts, and explicit dirty
builds from the active content repo.

@SamWilsn SamWilsn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only reviewed config.rs, find_root.rs, and main.rs so far, and only for minutia (no architecture or even "is this the right path?" yet).


Each pull request should be one feature, or else they're way too big for me to thoroughly review. This pull request adds:

  • workspaces
  • system command detection
  • support for local/remote themes+other repo
  • dirty git working trees

I hate to give you even more work, because I really appreciate what you've done so far, but there's no way I can give your contributions the attention they deserve when they're this dense.


Random thought, but would something like config be useful for organizing the different ways of specifying configuration options?

Comment thread src/config.rs
Comment on lines +109 to +111
pub default_profile: Option<String>,
pub build_root_base: PathBuf,
pub profiles: HashMap<String, LocalProfile>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not normally a stickler for doc comments, but because these (presumably) end up in a user facing config file, I think they deserve some.

@ritorhymes ritorhymes Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll add that. It's not currently in the generated config file today, since that starter text is still hand-written, but I agree it is still a user-facing schema and worth documenting.

Comment thread src/config.rs
Comment on lines +154 to +162
impl Default for WorkspaceConfig {
fn default() -> Self {
Self {
default_profile: Some(DEFAULT_PROFILE.into()),
build_root_base: DEFAULT_BUILD_ROOT_BASE.into(),
profiles: HashMap::new(),
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my sanity, lets keep the impls near the structs:

Suggested change
impl Default for WorkspaceConfig {
fn default() -> Self {
Self {
default_profile: Some(DEFAULT_PROFILE.into()),
build_root_base: DEFAULT_BUILD_ROOT_BASE.into(),
profiles: HashMap::new(),
}
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will do.

Comment thread src/config.rs
pub default_profile: Option<String>,
pub build_root_base: PathBuf,
pub profiles: HashMap<String, LocalProfile>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}
impl Default for WorkspaceConfig {
fn default() -> Self {
Self {
default_profile: Some(DEFAULT_PROFILE.into()),
build_root_base: DEFAULT_BUILD_ROOT_BASE.into(),
profiles: HashMap::new(),
}
}
}

Comment thread src/config.rs
Comment thread src/config.rs

pub fn from_path(path: &Path) -> Result<Self, WorkspaceError> {
let path = path.canonicalize().context(FsSnafu {
path: path.to_path_buf(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is to_path_buf required here? I believe snafu uses Into, but I could be wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just verified, you're right, it's not required, I'll clean it up.

Comment thread src/main.rs
Comment on lines +483 to +484
let curl = command_path("curl");
let wget = command_path("wget");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use reqwest (or reqwest::blocking) or ureq. We don't need to shell out to these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the direction if the install/update path shifts so Rust owns downloads rather than scripts/dev-setup handling first-install in shell. That shift naturally raises additional decisions around release distribution, integrity/verification, and retry/TLS handling, and a crate like reqwest or ureq is the right fit once a Rust-side consumer exists. I'm open to exploring that if you want to move in that direction now, but it would be a significant scope expansion for this reroll.

Comment thread src/main.rs
),
(None, None) => report.record(
DoctorStatus::Warn,
"missing both `curl` and `wget`; `scripts/dev-setup` will not be able to download a release binary",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Should this have been included in a later pull request?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looks like it. I’ll clean that up.

Comment thread src/main.rs
);
check_optional_download_tool(&mut report);

match command_path("tar") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is indeed necessary, tar is a good crate.

@ritorhymes ritorhymes Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the direction if the install/update path shifts so Rust owns download and unpack rather than scripts/dev-setup handling first-install in shell. That shift brings its own decisions around release distribution, integrity/verification, and self-replacing-binary semantics on Windows, and the tar crate becomes the right fit once a Rust-side consumer exists. I'm open to exploring that if you want to move in that direction now, but it would be a significant scope expansion for this reroll.

Comment thread src/main.rs
.transpose()?,
};

let use_staging = args.staging

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an equivalent --no-staging flag?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the right answer is to make these explicit CLI overrides rather than one-way additive flags. In the reroll I’m planning to treat staging (and similarly allow_dirty) as a tri-state override: --staging forces on, --no-staging forces off, and if neither is passed, behavior defers to the selected profile or built-in default.

Comment thread src/main.rs
let args = Args::parse();
if let Operation::Print { print } = args.operation {
print::print(print);
fn clone_missing_repo(url: &str, destination: &Path) -> Result<(), Whatever> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it should be in the git module

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@ritorhymes

Copy link
Copy Markdown
Contributor Author

@SamWilsn thanks for your review so far.

I am working on a new reroll as we speak based on your specific feedback. I'm going to try and split the rerolled version of this PR into separate PR's based on the features you outlined, TBD how clean or messy that may look since we're splitting a system up.

I expect to have it ready within the next couple of days.

I'll be dropping additional comments and replies to your review in this thread in the meantime.

@ritorhymes

ritorhymes commented Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

I care about making this idiomatic and maintainable. Rust isn’t my primary language (TypeScript is my home), so please do keep flagging those conventions and preferences when you see them.

Where there are more idiomatic or efficient patterns I should be using, or other changes that would make the code more legible to you and others, I’m very open to that guidance and I’ll make a point of incorporating it.

@ritorhymes

Copy link
Copy Markdown
Contributor Author

Random thought, but would something like config be useful for organizing the different ways of specifying configuration options?

I looked into config carefully, and I do not think it is a natural fit for build-eips in its current architecture or for the current direction of the local build system.

My reasoning:

  • build-eips does not currently need a general layered-config system. The local workflow is built around one persistent workspace file (.build-eips.toml), a small set of CLI path overrides, and the baked-in production/staging constants.
  • Profiles here are selected named configurations, not a stack of layered overrides. I would still need custom profile-resolution logic on top of config, so it would not actually remove the domain-specific complexity.
  • We already have figment in the binary for eipw.toml handling. Adding config would mean three different configuration paths in one binary: figment, manual serde/toml, and config. I would rather collapse toward fewer, not add a third.
  • The main capabilities config would add are env-var layering and broader source/format support. I do not think either is a strong need for the local workspace model.
  • I think it would make the resolution rules more abstract for contributors without buying much in return. Right now the intended local UX is: open .build-eips.toml, pick or edit a profile, and run build-eips.

So my current leaning is to skip config for this work. If the tool later grows into a true multi-source configuration problem, or if we decide we want env-var/config-location layering as a first-class feature, then I think config would be worth reconsidering.

@ritorhymes

Copy link
Copy Markdown
Contributor Author

I'd rather we just document the build-eips command examples in a readme/guide. Adding a whole other build tool just to run a single command feels weird.
Is the intent here to make running from a subdirectory more ergonomic? If so, I think that's something we should fix from within build-eips, not in an external tool.
I'm not entirely sure how I'd want the CLI to look (I don't even know what profiles are yet), but we could support aliases within build-eips for these shortcuts as easily as just (eg. just parity-serve becomes build-eips parity-serve).


Is the intent here to make running from a subdirectory more ergonomic?

Yes, that is a core part of it. The intent is to make running from inside EIPs/ or ERCs/ ergonomic, but also to avoid repeatedly spelling out profile and local workspace path boilerplate in normal local development workflows.

...we could support aliases within build-eips for these shortcuts as easily as just (eg. just parity-serve becomes build-eips parity-serve).

I agree, that is a wayyyyy better approach. I'm planning to drop justfile and move that shortcut surface into build-eips in the reroll. I kept it separate initially because I didn't want to expand the binary's CLI surface area without a clear signal that that approach might be welcome.


(I don't even know what profiles are yet)

A mode is how the build behaves (staging, dirty, parity, etc.); a profile is a named instance of a mode, a bundle of settings in .build-eips.toml that puts the system into that mode. parity and dirty are the built-in profiles; users can define more in their config.

I'm not entirely sure how I'd want the CLI to look...

Allow me to give you a semi-quick breakdown of the vision and direction of this system's dev experience:

  1. Be ergonomically developer-friendly by avoiding long chains of flags, memorized file and directory paths, or custom shell scripts for essential workflows like building and serving.
  2. Accept that the system is inherently complex, but contain that complexity behind an opinionated set of options that covers the common and sensible workflows while still allowing meaningful flexibility for more specialized cases within the same system.

Anti-pattern direction
What I want to avoid is the classic docs-tooling anti-pattern where the real workflow exists only as a long ritual command or private local shell script. For example, during Linux Kernel Docs work I was using commands shaped like:

make -C /root/bench/linux-stock \
  O=/root/bench/docs-stock \
  SPHINXBUILD=/root/techstuff/linux/.venv/bin/sphinx-build \
  SPHINXOPTS='-vv' \
  htmldocs

That may be technically correct, but it is not a good contributor-facing interface. My goal here is to keep build-eips expressive enough for this system’s different local modes without turning the CLI into a flag frenzy or making people rely on local wrapper scripts just to get normal work done.

Influence from Jekyll
Part of what Jekyll gets right is that repeated run-time flags can be moved into _config.yml, so the regular build or dev-server commands reflect the profile you want to run under without having to restate the same flag bundle each time.

This system is naturally more complex than that, so I am not trying to force it into a single generic config model. The local profiles in .build-eips.toml are closer to mode-specific config overlays for the different local build use cases.

The goal is still the same: avoid making contributors type or reconstruct commands like:

build-eips -C /work/EIPs-project/EIPs --profile dirty --theme-path /work/EIPs-project/theme --other-repo-path /work/EIPs-project/ERCs --build-root /work/EIPs-project/.local-build/EIPs serve

Rust, Zola, and the multi-repo model do raise the bar relative to something like Jekyll. I think that makes it more important that we offset the added complexity by reducing friction with a more streamlined command surface.

Ergonomic CLI direction from modern frameworks

I think that the CLI structure of modern frameworks like nextjs/astro/vue etc is wonderfully simple and a directional aspiration (mind you that complex monorepos may have their own elaborate and verbose CLI direction, but that is a different case entirely).

With NextJS, you can go from 0 to a running dev server in two commands with four words:

pnpm install
pnpm dev

Dev server is running!
Maybe add one flag so I can test builds on a mobile device.

pnpm dev --hostname 0.0.0.0

build-eips is a different system with its own concerns and complexity and should be handled accordingly. The mental model I have here is that the entrypoint commands for build workflows should lean closer to modern frameworks than the Linux Kernel, with .build-eips.toml playing the Jekyll _config role so flags don't have to be restated on every invocation.

@SamWilsn

Copy link
Copy Markdown
Contributor

That makes a lot of sense. build-eips wasn't really designed as a user-facing build tool. It started as a replacement for our GitHub Actions workflows that just happened to work locally some times, so it's got a lot of rough edges.

I do value user experience quite a bit but I also have to balance that against maintenance burden. Is there a single workspace layout we can just mandate and make it unconfigurable?

Ideally I'd like the CLI to be as simple as Zola's, eventually. Maybe build-eips init to clone the relevant repos into the correct layout, then build-eips check, build-eips build, and build-eips serve. I'm not sure supporting more flexibility gives us much benefit, but I could be entirely wrong!

@ritorhymes

Copy link
Copy Markdown
Contributor Author

I do value user experience quite a bit but I also have to balance that against maintenance burden. Is there a single workspace layout we can just mandate and make it unconfigurable?

Yeah, we can pare back support for the out-of-tree style configuration options. No one will die.

I'm not sure supporting more flexibility gives us much benefit, but I could be entirely wrong!

If you don't think more flexibility buys much, I'm not inclined to push back here. My thesis is mainly around the baseline devEx being reasonably usable locally. While I can imagine various use cases being useful based on practical contexts, we don't have a proven demand for all of them or a clear critical need that would justify more complex maintenance. So if you don't see the benefit and no one is screaming for it (that I'm aware of), I'll see what I can reasonably scale back. I could be wrong too here, but we could always expand the product surface later if people do scream about it.

Ideally I'd like the CLI to be as simple as Zola's, eventually. Maybe build-eips init to clone the relevant repos into the correct layout, then build-eips check, build-eips build, and build-eips serve

I'm going to take another pass (I was almost finished with the v2 reroll) to rework it closer to the above, though I'd expect there will still be flags / configs in some cases.

@ritorhymes ritorhymes closed this May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants