Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const moduleRoot = "^\\.dependency-cruiser-rust/modules/src/";
const layerRoot = "^\\.dependency-cruiser-rust/layers/src/";

const modulePath = (pattern) => `${moduleRoot}${pattern}`;

const interfaceModules = "(?:cli|onboarding|tui)(?:/|\\.js$)";
const foundationalModules = "(?:(?:config|templates|rustpatch|fsutil|strings|process)(?:/|\\.js$)|error\\.js$)";
const applicationModules = "(?:cli|onboarding|bootstrap|runtime|plugin|codegen|scaffold|toolchain|workspace|tui)(?:/|\\.js$)";

/** @type {import("dependency-cruiser").IConfiguration} */
module.exports = {
forbidden: [
{
name: "no-circular-rust-modules",
severity: "error",
comment: "Rust source modules must not form circular dependency chains.",
from: { path: moduleRoot },
to: { circular: true },
},
{
name: "no-circular-rust-layers",
severity: "error",
comment: "Top-level Rust architecture modules/layers must not depend on each other cyclically.",
from: { path: layerRoot },
to: { circular: true },
},
{
name: "lower-layers-must-not-import-interface",
severity: "error",
comment: "Only the interface layer may depend on cli/onboarding/tui modules.",
from: {
path: modulePath("(?!(?:main|lib)\\.js$)(?!(?:cli|onboarding|tui)(?:/|\\.js$)).+"),
},
to: {
path: modulePath(interfaceModules),
},
},
{
name: "foundational-modules-stay-foundational",
severity: "error",
comment: "Shared foundation modules must not depend on application or interface modules.",
from: {
path: modulePath(foundationalModules),
},
to: {
path: modulePath(applicationModules),
},
},
{
name: "codegen-must-not-import-runtime",
severity: "error",
comment: "Runtime may orchestrate codegen, but codegen must not depend on runtime.",
from: {
path: modulePath("codegen(?:/|\\.js$)"),
},
to: {
path: modulePath("runtime(?:/|\\.js$)"),
},
},
{
name: "plugin-runtime-boundary",
severity: "error",
comment: "Plugin runtime must stay independent from CLI, runtime orchestration, generators, scaffolders, and template internals.",
from: {
path: modulePath("plugin(?:/|\\.js$)"),
},
to: {
path: modulePath("(?:cli|onboarding|runtime|codegen|scaffold|templates|toolchain|tui|rustpatch|fsutil)(?:/|\\.js$)"),
},
},
{
name: "workspace-boundary",
severity: "error",
comment: "Workspace discovery/model code must stay below application capabilities and depend only on low-level shared modules.",
from: {
path: modulePath("workspace(?:/|\\.js$)"),
},
to: {
path: modulePath("(?:cli|onboarding|runtime|plugin|codegen|scaffold|templates|toolchain|tui|rustpatch|fsutil|strings)(?:/|\\.js$)"),
},
},
{
name: "toolchain-runtime-boundary",
severity: "error",
comment: "Toolchain code may use the shared process boundary, but must not couple to runtime orchestration.",
from: {
path: modulePath("toolchain(?:/|\\.js$)"),
},
to: {
path: modulePath("runtime(?:/|\\.js$)"),
},
},
],
options: {
doNotFollow: {
path: "node_modules",
},
enhancedResolveOptions: {
extensions: [".js"],
},
reporterOptions: {
dot: {
collapsePattern: "node_modules/[^/]+",
},
},
},
};
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Rust build artifacts
/target

# Node tooling used by architecture validation
/node_modules/
/.dependency-cruiser-rust/

# Editor / OS
.DS_Store
*.swp
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The docs site source lives in [`docs/site/`](docs/site/) and is published from `
| Concepts | [`Architecture`](docs/site/src/concepts/architecture.md), [`Workspace model`](docs/site/src/concepts/workspace-model.md), [`Build pipeline`](docs/site/src/concepts/build-pipeline.md), [`Plugin runtime`](docs/site/src/concepts/plugin-runtime.md), [`Anchor vs Pinocchio`](docs/site/src/concepts/framework-pinocchio-vs-anchor.md) | The mental model behind the CLI |
| Contributing | [`Roadmap`](docs/site/src/contributing/roadmap.md), [`Architecture decisions`](docs/site/src/contributing/adrs.md), [`Developer setup`](docs/site/src/contributing/dev-setup.md), [`Documentation style`](docs/site/src/contributing/docs-style.md) | Project status, ADRs, local development, and docs conventions |

Operational repo references are also kept in [`docs/reference/`](docs/reference/): [`markers`](docs/reference/markers.md), [`codegen`](docs/reference/codegen.md), [`recipes`](docs/reference/recipes.md), [`onboarding`](docs/reference/onboarding.md), [`app/plugins`](docs/reference/app.md), [`Pinocchio`](docs/reference/pinocchio.md), [`testing`](docs/reference/testing.md), and [`distribution`](docs/reference/distribution.md).
Operational repo references are also kept in [`docs/reference/`](docs/reference/): [`markers`](docs/reference/markers.md), [`codegen`](docs/reference/codegen.md), [`recipes`](docs/reference/recipes.md), [`onboarding`](docs/reference/onboarding.md), [`app/plugins`](docs/reference/app.md), [`Pinocchio`](docs/reference/pinocchio.md), [`testing`](docs/reference/testing.md), [`dependency analysis`](docs/reference/dependency-analysis.md), and [`distribution`](docs/reference/distribution.md).

---

Expand Down Expand Up @@ -224,6 +224,7 @@ cargo test # run unit + golden tests
cargo clippy -- -D warnings
cargo fmt --check
cargo bench --bench cold_start
npm run arch:deps # dependency analysis with Dependency Cruiser
mdbook build docs/site
mdbook serve docs/site --open
bash scripts/integration-heavy.sh
Expand All @@ -236,12 +237,14 @@ Project layout:

```
src/
bootstrap.rs # shared workspace bootstrap for chain new and onboarding
cli/ # clap command surface (root, version, doctor, ...)
codegen/ # Codama config, IDL export, frontend hook generation
config/ # sunscreen.yml schema, loader, migrations
onboarding/ # init, quickstart, examples, wallet, deploy, learn flows
plugin/ # plugin manifests, marketplace, sandbox, stdio/gRPC adapters
runtime/ # subprocess, build pipeline, watcher, validator supervisor
process.rs # shared subprocess runner and process boundary
runtime/ # build pipeline, watcher, validator supervisor
scaffold/ # composite CRUD/SPL token/Metaplex NFT recipes
strings/ # centralized user-facing strings
toolchain/ # external tool detection (anchor, solana, ...)
Expand Down
132 changes: 132 additions & 0 deletions docs/reference/dependency-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Dependency analysis

Sunscreen validates architectural dependencies with
[Dependency Cruiser](https://github.com/sverweij/dependency-cruiser). The check
is intentionally limited to dependency analysis:

- circular dependencies between Rust modules and top-level layers;
- forbidden imports between discovered project layers;
- layer boundary violations.

It does not calculate coupling metrics, run SonarQube, run mutation testing, or
modify production Rust code.

## Local command

```bash
npm install
npm run arch:deps
```

`dependency-cruiser` analyzes JavaScript/TypeScript projects natively, not Rust.
For this Rust crate, `scripts/dependency-cruiser-rust-adapter.mjs` generates a
temporary JavaScript shadow graph under `.dependency-cruiser-rust/` from
`crate::...` imports. Dependency Cruiser then validates that graph with
`.dependency-cruiser.cjs`.

## Discovered layers

The rules are based on the current source tree and
`docs/site/src/concepts/architecture.md`, not on a generic Clean Architecture
template.

| Layer | Paths |
|-------|-------|
| Interface/entry | `src/main.rs`, `src/cli/`, `src/onboarding/`, `src/tui/` |
| Application capabilities | `src/bootstrap.rs`, `src/runtime/`, `src/plugin/`, `src/codegen/`, `src/scaffold/`, `src/toolchain/`, `src/workspace/` |
| Shared foundation | `src/config/`, `src/templates/`, `src/rustpatch/`, `src/fsutil/`, `src/process.rs`, `src/strings/`, `src/error.rs` |

## Rules

### `no-circular-rust-modules`

Fails on circular dependency chains between Rust source modules in the generated
module graph.

Example detected pattern:

```text
src/cli/onboarding.rs -> src/onboarding/wizard.rs -> src/cli/onboarding.rs
```

### `no-circular-rust-layers`

Fails on circular dependency chains between top-level Rust modules/layers.

Example detected pattern:

```text
src/codegen/* -> src/runtime/* -> src/codegen/*
```

### `lower-layers-must-not-import-interface`

Forbids non-interface modules from importing `cli`, `onboarding`, or `tui`.
The command surface may orchestrate lower layers, but lower layers should not
reach back into command parsing or UI modules.

Example detected pattern:

```text
src/runtime/pipeline.rs -> src/cli/chain.rs
```

### `foundational-modules-stay-foundational`

Forbids shared foundation modules from depending on application capability or
interface modules. Foundation modules should stay reusable and low-level.

Example detected pattern:

```text
src/config/schema.rs -> src/cli/root.rs
```

### `codegen-must-not-import-runtime`

Allows runtime orchestration to call code generation, but forbids codegen from
depending back on runtime. This protects the build pipeline from becoming a
bidirectional dependency.

Example detected pattern:

```text
src/codegen/codama.rs -> src/runtime/subprocess.rs
```

### `plugin-runtime-boundary`

Keeps the plugin runtime independent from CLI, runtime orchestration,
generators, scaffolders, templates, toolchain logic, TUI, rustpatch, and fsutil.
The current intended dependencies are plugin internals plus config, workspace,
and error types.

Example detected pattern:

```text
src/plugin/manager.rs -> src/cli/app.rs
```

### `workspace-boundary`

Keeps workspace discovery/model code below application capabilities. It may use
low-level shared modules, but it should not depend on command, runtime,
generator, plugin, scaffold, template, toolchain, or UI modules.

Example detected pattern:

```text
src/workspace/mod.rs -> src/runtime/pipeline.rs
```

### `toolchain-runtime-boundary`

Allows `toolchain` to use the shared `process` boundary for testable command
execution, but forbids coupling toolchain detection or repair code to runtime
orchestration modules.

Example detected pattern:

```text
src/toolchain/fix.rs -> src/runtime/supervisor.rs
```
4 changes: 3 additions & 1 deletion docs/site/src/concepts/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The CLI layer is thin: it parses, validates, then hands off to the runtime or te

[`src/runtime/`](https://github.com/Pantani/sunscreen/tree/main/src/runtime). Owns:

- **Subprocess management** (`subprocess.rs`) — `CommandSpec`, `ProcessRunner`, `SubprocessRunner`. Every shell-out goes through this for testability.
- **Build/runtime orchestration** uses the shared subprocess boundary in `src/process.rs` (`CommandSpec`, `ProcessRunner`, `SubprocessRunner`) so every shell-out stays testable without coupling generators back to runtime.
- **Build pipeline** (`pipeline.rs`) — anchor build → IDL export → Codama → frontend notify.
- **Watcher** (`watcher.rs`) — `notify` events, debounced, dedup'd, filtered.
- **Validator adapters** (`surfpool.rs`, `testvalidator.rs`) — abstracted behind a `LocalValidator` trait.
Expand Down Expand Up @@ -52,6 +52,8 @@ The gRPC contract in `proto/plugin.proto` is wire-defined but not yet end-to-end
| Concern | Where |
|---------|-------|
| Config (`sunscreen.yml`) | [`src/config/`](https://github.com/Pantani/sunscreen/tree/main/src/config) — schema, loader, migrations |
| Workspace bootstrap | [`src/bootstrap.rs`](https://github.com/Pantani/sunscreen/blob/main/src/bootstrap.rs) — shared `chain new`, `init`, examples, and quickstart workspace creation |
| Process execution | [`src/process.rs`](https://github.com/Pantani/sunscreen/blob/main/src/process.rs) — shared subprocess runner boundary |
| Toolchain detection | [`src/toolchain/`](https://github.com/Pantani/sunscreen/tree/main/src/toolchain) — uniform `ToolReport` for every external tool |
| Errors | [`src/error.rs`](https://github.com/Pantani/sunscreen/blob/main/src/error.rs) — single `Error` enum, `code` + `next_step` |
| TUI (chain serve) | [`src/tui/serve_model.rs`](https://github.com/Pantani/sunscreen/blob/main/src/tui/serve_model.rs) |
Expand Down
Loading
Loading