Skip to content

Commit fcce903

Browse files
Merge pull request #50 from frantic1048/dev/blend-create-add
feat!(blend): add order create and add commands
2 parents cf51086 + a87349d commit fcce903

14 files changed

Lines changed: 1123 additions & 36 deletions

File tree

AGENTS.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
Vanilla is the owner's dotfiles repository. Configs are defined as Nickel
66
orders under `orders/` and deployed by the local `blend` CLI, a Rust tool in
7-
`blend/`. The root `bin/blend` entry is a symlink to the release build at
7+
`blend/`. The root `bin` entry is a symlink to `orders/bin/bin`, and
8+
`bin/blend` resolves through that Source directory to the release build at
89
`target/release/blend`.
910

1011
This repo mixes two surfaces:
@@ -18,7 +19,7 @@ Keep those surfaces distinct when changing, testing, and interpreting CI.
1819

1920
- `blend/` - Rust crate for the `blend` CLI.
2021
- `orders/` - active Nickel order definitions and config source files.
21-
- `bin/` - personal scripts deployed to `$PATH`, plus the `bin/blend` symlink.
22+
- `bin` - symlink to the physical personal-script Source at `orders/bin/bin`.
2223
- `legacy/` - stow-era or out-of-scope files kept for reference only; not
2324
managed by blend.
2425
- `screenshots/` - README screenshots.
@@ -86,7 +87,7 @@ Toolchain and dependency facts:
8687
Common tasks:
8788

8889
```sh
89-
just build # release build + update bin/blend symlink
90+
just build # release build + update the bin/blend entrypoint symlink
9091
just check # bin/blend check
9192
just test # cargo test --release in blend/
9293
just fmt-check # cargo fmt --check in blend/
@@ -112,6 +113,10 @@ Inspect commands:
112113
Maintain commands:
113114

114115
- `check [orders...]` - `[read]` validate Source order definitions.
116+
- `create <order>` - `[source]` scaffold a new empty Source order.
117+
- `add <order> <target>` - `[source]` import an absolute or `~`-prefixed
118+
Target file/directory into an existing Source order. Useful flags:
119+
`--prefix`, `--symlink follow|preserve`, `--allow-overlap`.
115120
- `format [orders...]` / `fmt` - `[source]` format Source order files; use
116121
`--check` in CI or review validation.
117122
- `init --upgrade` - `[source, target]` initialize or refresh
@@ -129,6 +134,13 @@ Global flags:
129134
- `--blend-dir` overrides the Blend Source root.
130135
- `--sandbox force|prefer|never` controls the process sandbox policy.
131136

137+
Order Source paths:
138+
139+
- `from_file` and `local` are relative to their order directory and must not be
140+
absolute or normalize outside that directory.
141+
- File entries need an effective Target prefix from either `blend.prefix` or
142+
entry-level `prefix`.
143+
132144
## Source Map
133145

134146
For blend code changes, start here:

ERGO.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
## Context
44

5-
Investigating the current blend implementation (at `~/Vanilla/blend/`) to map essential user journeys and identify friction points. blend is a Rust-based dotfiles manager using Nickel DSL, managing ~55 orders across macOS and Linux.
5+
Investigating the current blend implementation (at `~/Vanilla/blend/`) to map essential user journeys and identify friction points. blend is a Rust-based dotfiles manager using Nickel DSL, managing roughly 50 orders across macOS and Linux.
66

7-
**Current CLI commands:** default status, `sync` (alias `s`), `view`, `table`,
8-
and `init`. The previous `ship`, `sample`, and task/upgrade CLI paths have been
9-
removed or moved to the top-level `justfile`.
7+
**Current CLI commands:** default status, `status`, `view`, `table`, `check`,
8+
`create`, `add`, `format` (alias `fmt`), `init`, and `sync` (alias `s`). The
9+
previous `ship`, `sample`, and task/upgrade CLI paths have been removed or
10+
moved to the top-level `justfile`.
1011

1112
---
1213

@@ -50,10 +51,11 @@ removed or moved to the top-level `justfile`.
5051
### Current Flow
5152

5253
```
53-
1. Create dir: mkdir orders/my-app
54-
2. Write order.ncl: (manually, from memory or by copying another order)
55-
3. For plaintext: cp ~/.config/my-app/config orders/my-app/config
56-
4. For structured: Manually transcribe TOML/JSON/YAML into Nickel from_config syntax
54+
1. Scaffold order: blend create my-app
55+
2. Import plaintext: blend add my-app ~/.config/my-app/config
56+
2a. Or explicit root: blend add my-app --prefix ~/.config/my-app ~/.config/my-app/config
57+
3. For structured: Manually transcribe TOML/JSON/YAML into Nickel from_config syntax
58+
4. Validate: blend check my-app
5759
5. Preview: blend view my-app
5860
6. Deploy: blend sync my-app
5961
```
@@ -62,19 +64,18 @@ removed or moved to the top-level `justfile`.
6264

6365
| # | Issue | Severity | Detail |
6466
|---|-------|----------|--------|
65-
| 1 | **No scaffolding command** | High | No `blend add my-app` to create order skeleton with boilerplate order.ncl |
67+
| 1 | **~~No scaffolding command~~** | ~~High~~ | **Resolved**`blend create <order>` scaffolds the Source order and `blend add <order> <target>` imports existing Target files/directories |
6668
| 2 | **Manual config transcription for structured** | High | User must hand-convert a TOML/JSON file into Nickel `from_config = { ... }` syntax. For a 200-line starship.toml, this is painful and error-prone |
6769
| 3 | **Must know Nickel syntax** | Medium | No inline documentation, no `blend help new-order` with examples |
68-
| 4 | **No first-class validation command** | Low | `just check` wraps `bin/blend view --dry-run`, but there is no dedicated `blend check`/`blend lint` CLI yet |
70+
| 4 | **~~No first-class validation command~~** | ~~Low~~ | **Resolved**`blend check [orders...]` validates Source order definitions without deploying |
6971
| 5 | **Schema contract usage is implicit** | Low | User should pipe to `| Order` at end of order.ncl for editor/Nickel validation, but the workflow does not strongly suggest it; Rust deserialization still validates the evaluated shape |
7072

7173
### Improvement Ideas
7274

73-
- `blend add <name> [--from <path>]` command that:
74-
- Creates `orders/<name>/order.ncl` with sensible defaults
75-
- If `--from ~/.config/app/config.toml` is given: auto-detects format, parses the file, generates `from_config` Nickel syntax using `json_to_nickel()` (already implemented in `ast_utils.rs`)
76-
- For directories: creates `from_file` entry pointing to copied dir
77-
- `blend check`: validate all order.ncl files without deploying (first-class CLI wrapper around fast Nickel eval + schema check)
75+
- ~~Order scaffolding/import~~: **Implemented** via `blend create <order>` and
76+
`blend add <order> <target>` for `from_file` entries.
77+
- Structured import remains future work: auto-detect a TOML/JSON/YAML Target,
78+
parse it, and generate `from_config` Nickel syntax using `json_to_nickel()`.
7879

7980
---
8081

@@ -158,14 +159,14 @@ These friction points from the original analysis have been addressed by `blend s
158159

159160
| # | Issue | Severity | Detail |
160161
|---|-------|----------|--------|
161-
| 1 | **No first-class validation-only command** | Medium | No `blend check` or `blend lint` CLI yet; the top-level `just check` currently uses `bin/blend view --dry-run` |
162+
| 1 | **~~No first-class validation-only command~~** | ~~Medium~~ | **Resolved**`blend check [orders...]` validates Source order definitions without deploying |
162163
| 2 | **No rollback** | Medium | If a force deploy overwrites a config and breaks an app, there's no `blend rollback` or automatic backup |
163164
| 3 | **Nickel errors can be opaque** | Low | Nickel evaluation errors include source info but can be hard to trace for contract violations |
164165
| 4 | **No pre-sync backup** | Low | Sync overwrites in-place. A backup of the previous deployed version would help recovery |
165166

166167
### Improvement Ideas
167168

168-
- `blend check`: validate all orders without building (fast Nickel eval + schema check)
169+
- ~~`blend check`~~: **Implemented**validate all orders without deploying (fast Nickel eval + schema check)
169170
- Auto-backup before Source -> Target sync: copy previous Target file to `~/.cache/blend/backups/<order>/<file>.bak`
170171
- `blend rollback <order>`: restore from backup
171172

@@ -176,10 +177,12 @@ These friction points from the original analysis have been addressed by `blend s
176177
### Quick Wins (low effort, high impact)
177178
1. ~~**Fix bootstrap script**: install proto/Rust, build `bin/blend`, then deploy via `just bootstrap`~~
178179
2. **First-run message**: When all orders are pending, show "Run `blend sync` to review and deploy"
179-
3. **`blend check` command**: Validate all order.ncl files without deploying
180+
3. ~~**`blend check` command**~~: **Implemented** — validate all order.ncl files without deploying
180181

181182
### Medium Effort
182-
4. **`blend add <name> [--from <path>]`**: Scaffold new orders with auto-import from existing deployed configs (can reuse existing `json_to_nickel()` for format conversion). This covers the "capture existing config into a new order" use case — currently there's no way to pull a config from the filesystem into a new order without manual setup.
183+
4. ~~**`blend create` / `blend add` for `from_file` import**~~:
184+
**Implemented** — remaining work is structured `from_config` import from
185+
existing TOML/JSON/YAML Targets.
183186
5. **`--no-rewrite` info display**: Show branch context and Nickel snippets for manual merge
184187
6. **Suggest ignore patterns**: Auto-detect frequently changing fields
185188

@@ -195,6 +198,9 @@ These friction points from the original analysis have been addressed by `blend s
195198
Features that were in "Improvement Ideas" and are now implemented:
196199

197200
- **`blend sync`** — bidirectional sync with interactive Source/Target/skip choices (Journey 3, items 1/3/4)
201+
- **`blend check`** — validation-only Source order checks
202+
- **`blend create`** — scaffold an empty Source order
203+
- **`blend add`** — import existing Target files/directories as `from_file` entries
198204
- **`blend sync --force-source-to-target`** — non-interactive Source -> Target all
199205
- **`blend sync --force-target-to-source`** — non-interactive Target -> Source all
200206
- **Surgical .ncl rewrite** — auto-patches Nickel source for data-only and conditional values

NEW_BLEND.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Two config modes per file entry:
3535
| Mode | Source | Rendering | Sync-back |
3636
|------|--------|-----------|-----------|
3737
| `from_config` | Inline Nickel data/expressions | Evaluated → rendered to target format | Context-aware AST rewrite |
38-
| `from_file` | Files/dirs in `orders/<order>/` | Copied as-is | File copy back |
38+
| `from_file` | Relative files/dirs inside `orders/<order>/` | Copied as-is | File copy back |
3939

4040
---
4141

@@ -60,14 +60,14 @@ Each order is defined by `orders/<order>/order.ncl`. The evaluated result must c
6060
|-------|------|----------|-------------|
6161
| `name` | String | Yes (for `from_config`) | Destination filename. Combined with prefix for target path. Auto-set from `from_file` if omitted. |
6262
| `from_config` | Record/Array | One of these | Inline structured config data, evaluated by Nickel and rendered to target format |
63-
| `from_file` | String | One of these | Path to file/directory in the order dir, copied as-is |
64-
| `prefix` | Array<String> | No | Per-file prefix override (default: inherits global `blend.prefix`) |
63+
| `from_file` | String | One of these | Relative path to file/directory in the order dir, copied as-is. Absolute paths and paths that normalize outside the order dir are rejected. |
64+
| `prefix` | Array<String> | No | Per-file prefix override (default: inherits global `blend.prefix`). File entries require an effective prefix from either place. |
6565
| `format` | String | No | Output format override (default: inferred from `name` extension) |
6666
| `ignore` | Array<String> | No | Keys/patterns to exclude from diff (merged with global) |
6767
| `when` | Record | No | Per-file condition: `{ os, arch, hostname }` |
6868
| `symlink` | Bool | No | Create symlink instead of copying (`from_file` only) |
6969
| `exclude` | Array<String> | No | Glob patterns to skip in `from_file` directories |
70-
| `local` | String | No | Local overlay directory for machine-specific overrides (auto-created, gitignored) |
70+
| `local` | String | No | Relative local overlay directory for machine-specific overrides (auto-created, gitignored). It follows the same non-escaping Source path rule as `from_file`. |
7171
| `immutable` | Bool | No | Set OS immutable flag after deploying (macOS `chflags uchg`, Linux `chattr +i`) |
7272

7373
### Example: structured config (from_config)
@@ -325,7 +325,7 @@ Prompt diffs use explicit side markers instead of traditional `+/-`:
325325

326326
```
327327
blend Status: show all orders and sync state
328-
blend sync [orders...] Interactive bidirectional sync (default)
328+
blend sync [orders...] Interactive bidirectional sync
329329
blend s [orders...] Alias for `blend sync`
330330
blend sync --force-source-to-target
331331
Force-apply Source values to Targets
@@ -337,6 +337,10 @@ blend view -c [orders...] Show generated content only (no diff)
337337
blend view -a [orders...] Show both content and diff
338338
blend view -s [orders...] Short mode: omit up-to-date entries
339339
blend check [orders...] Typecheck/evaluate order.ncl files
340+
blend create <order> Scaffold an empty Source order
341+
blend add <order> <target> Import a Target file/directory into an order
342+
blend add <order> --prefix <path> <target>
343+
Strip an explicit Target prefix before import
340344
blend format [orders...] Format order.ncl files
341345
blend format --check [orders...] Check order.ncl formatting without writing
342346
blend table Output order info as HTML table (for README)
@@ -453,7 +457,7 @@ In `.ncl` files, use `\u{xxxx}` escape sequences for non-ASCII characters (e.g.,
453457
- **Three-way merge context**: Snapshot-backed prompts are implemented for conflict explanation. This is not a full automatic merge engine, but `blend` can now show Source/Target/Base context when a snapshot exists.
454458
- **Secrets management**: Deferred to v2. Focus on core config management first.
455459
- **JSONC round-trip**: Output JSON without comments. Comments live in Nickel source.
456-
- **Schema validation**: Orders are validated through the Nickel `| Order` contract when present, then through Rust deserialization and `resolve_defaults()`. Generated `order.contract.ncl` and `metadata.ncl` freshness is checked for read-only commands and repaired by `init`/`sync`.
460+
- **Schema validation**: Orders are validated through the Nickel `| Order` contract when present, then through Rust deserialization, `resolve_defaults()`, and Source path checks. Generated `order.contract.ncl` and `metadata.ncl` freshness is checked for read-only commands and repaired by `init`/`sync`.
457461

458462
---
459463

blend/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ src/
2828
├── metadata.rs OS/arch/hostname/desktop/user detection
2929
├── output.rs log helpers (info/warn/error/success)
3030
31-
├── commands.rs re-exports cmd_sync / cmd_view / cmd_status / cmd_table
31+
├── commands.rs re-exports command handlers
3232
├── commands/
33+
│ ├── add.rs import Target files/directories into Source orders
34+
│ ├── create.rs scaffold empty Source orders
35+
│ ├── check.rs validate Source order definitions
36+
│ ├── format.rs format order.ncl files
3337
│ ├── helpers.rs shared symlink + diff-aggregation helpers
38+
│ ├── init.rs refresh generated contract + metadata files
3439
│ ├── sync.rs bidirectional sync + per-key interactive flow
3540
│ ├── view.rs render preview & diff
3641
│ ├── status.rs order state table (parallel via rayon)
@@ -60,7 +65,7 @@ src/
6065
└── text.rs line-based diffing for plaintext
6166
6267
tests/
63-
├── sync_e2e.rs end-to-end CLI tests (39 scenarios)
68+
├── sync_e2e.rs end-to-end CLI tests
6469
└── fixtures/ .ncl + deployed-file fixtures
6570
```
6671

blend/src/cli.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clap::{Parser, Subcommand};
1+
use clap::{Parser, Subcommand, ValueEnum};
22
use std::path::PathBuf;
33

44
use crate::sandbox::SandboxMode;
@@ -23,6 +23,8 @@ Inspect:
2323
2424
Maintain:
2525
check [read] Validate Source order definitions
26+
create [source] Create a new order
27+
add [source] Add a Target file or directory to an order
2628
format [source] Format Source order files
2729
init [source, target] Initialize or refresh Blend metadata and config
2830
sync [source, target] Reconcile Source orders and Target files
@@ -131,6 +133,37 @@ pub enum MaintainCommands {
131133
orders: Vec<String>,
132134
},
133135

136+
/// [source] Create a new order
137+
Create {
138+
/// Order to create
139+
order: String,
140+
},
141+
142+
/// [source] Add a Target file or directory to an existing order
143+
#[command(long_about = "\
144+
[source] Add a Target file or directory to an existing order
145+
146+
Copies the Target path into the order Source tree and appends a file entry to order.ncl.")]
147+
Add {
148+
/// Order to add the Target path to
149+
order: String,
150+
151+
/// Target file or directory to add; must be absolute or start with ~
152+
path: PathBuf,
153+
154+
/// Target prefix to strip before copying into the order Source tree
155+
#[arg(long)]
156+
prefix: Option<String>,
157+
158+
/// Symlink deployment policy; required when the Target path is a symlink
159+
#[arg(long)]
160+
symlink: Option<SymlinkMode>,
161+
162+
/// Allow overlapping Target paths with existing entries
163+
#[arg(long)]
164+
allow_overlap: bool,
165+
},
166+
134167
/// [source] Format Source order files
135168
#[command(alias = "fmt")]
136169
Format {
@@ -153,3 +186,11 @@ Writes or refreshes orders/order.contract.ncl and orders/metadata.ncl. For a new
153186
upgrade: bool,
154187
},
155188
}
189+
190+
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
191+
pub enum SymlinkMode {
192+
/// Copy resolved Target content into Source and deploy a real file/directory
193+
Follow,
194+
/// Copy resolved Target content into Source and deploy the Target as a symlink
195+
Preserve,
196+
}

blend/src/commands.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
pub mod add;
12
pub mod check;
3+
pub mod create;
24
pub mod format;
35
pub mod helpers;
46
pub mod init;
@@ -7,7 +9,9 @@ pub mod sync;
79
pub mod table;
810
pub mod view;
911

12+
pub use add::cmd_add;
1013
pub use check::cmd_check;
14+
pub use create::cmd_create;
1115
pub use format::cmd_format;
1216
pub use init::cmd_init;
1317
pub use status::cmd_status;

0 commit comments

Comments
 (0)