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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to `laranail/toolkit` are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Removed

- **Breaking: the `laranail::toolkit.tidy` command** (`Simtabi\Laranail\Toolkit\Commands\Tidy`) moved to `laranail/artisan-ui` as `laranail::artisan-ui.tidy`, with the same actions, options and safety rules. Install that package and call the new name; there is no forwarder here.
- **`routes/web.php` and `routes/api.php`.** No provider ever loaded them. `web.php` held seven unauthenticated `GET` closures that rebuilt and cleared caches (`/tidy/*`), and `api.php` was empty. Their actions are in `laranail/artisan-ui`'s authenticated Caches and Tidy quick-action groups.

### Changed

- The branch alias is `0.3.x-dev`. Consumers on `^0.2` stay on 0.2.x and keep `laranail::toolkit.tidy`; require `^0.3` to move.

## [0.2.3] - 2026-10-05

### Added
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
}
},
"branch-alias": {
"dev-main": "0.2.x-dev"
"dev-main": "0.3.x-dev"
}
},
"suggest": {
Expand Down
12 changes: 4 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,13 @@ src/

> The command base (`Command` + `SupportsNamespacedNames`) is **not** local — it
> comes from [`laranail/console`](https://opensource.simtabi.com/console/) `^1.0`,
> the org-canonical command base. All three toolkit commands — `MakeCrud`,
> `IdeHelperMacros`, `Tidy` — extend it and use its **full
> the org-canonical command base. Both toolkit commands — `MakeCrud` and
> `IdeHelperMacros` — extend it and use its **full
> feature set**: the fluent `$this->consoleWriter()` (context statuses
> success/error/warning/info/note, styling, emoji) and the `$this->services`
> lifecycle (`performance`, `signals`, `interaction`, `logger`, `error`,
> `metadata`, `display`). The heavy `Tidy` command makes its destructive sweep
> **signal-safe** (`signals()->shouldKeepRunning()`), confirms through
> `interaction()->confirmAction()` (safe default in non-interactive mode), and
> captures failures via the **auto-redacting** `error()->logError()` — so
> credentials never reach a log channel. The existing security hardening
> (FilePathGuard storage confinement, `db` action gating) is untouched.
> `metadata`, `display`). The maintenance command `Tidy` moved to `laranail/artisan-ui`
> (`laranail::artisan-ui.tidy`) in 0.3.0.

## Adding a feature / tool / module

Expand Down
84 changes: 7 additions & 77 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ alias), and injects its collaborators — no facades in the core logic.
|---|---|---|---|
| CRUD generator | `laranail::toolkit.make-crud` | `laranail::toolkit.make-crud` | [make-crud](make-crud.md) |
| IDE-helper macros | `laranail::toolkit.ide-helper-macros` | `laranail::toolkit.ide-helper-macros` | [macros](macros.md) |
| Tidy | `laranail::toolkit.tidy` | `tidy` | below |

---

Expand Down Expand Up @@ -62,90 +61,21 @@ non-zero. The non-interactive flag is read from the input, so `--no-interaction`
the stub size via `display()->formatBytes(strlen(...))`. Accepts `--path=` to
override the output location (default
`ide-helper/_ide_helper_macros.php` under the base path).
- **`tidy`** (heavy) — `consoleWriter()` throughout; the file sweep is
**signal-safe** (`shouldKeepRunning()` is polled per root and per file);
destructive prompts route through `interaction()->confirmAction()` (the existing
`db` gating is preserved); each run is timed with `performance()` and logged via
`logger()->logCompletion()` with files-processed + space-freed `metadata()`.

---

## `laranail::toolkit.tidy` — maintenance / cleanup
## Tidy moved to `laranail/artisan-ui`

Unified, path-confined cleanup: `cache`, `logs`, `temp`, `storage`, `db`, `all`.
The maintenance command `laranail::toolkit.tidy` was removed in 0.3.0. It now lives in
[`laranail/artisan-ui`](https://opensource.simtabi.com/documentation/laranail/artisan-ui/) as
`laranail::artisan-ui.tidy`, with the same actions, options and safety rules (storage-confined
deletion, scoped `storage` sweeps, `db` gated and kept out of `all`). Install that package and call
the new name:

```bash
php artisan laranail::toolkit.tidy [action] [options]
php artisan laranail::artisan-ui.tidy logs --days=30 --force
```

| Option | Description |
|---|---|
| `action` (argument) | `cache`, `logs`, `temp`, `storage`, `db`, or `all` (default). |
| `--days=` | Only delete files older than this many days. |
| `--size=` | Only delete files larger than this many MB. |
| `--seed` | (db) also run `db:seed` after `migrate:fresh`. |
| `--optimize` | (cache) also run `optimize:clear`. |
| `--dry-run` | Show what would be removed without deleting anything. |
| `--unfiltered` | (`storage`) sweep user files with no age/size filter. Refused in production. |
| `--force` | Skip confirmation prompts (required for the `db` action). |

### Actions

- **`cache`** — flushes the application cache; with `--optimize` also runs
`optimize:clear`. `--dry-run` previews; the freed space is reported.
- **`logs` / `temp` / `storage`** — delete files under the relevant
`storage_path()` roots, filtered by `--days` (age) and/or `--size`.
`.gitignore` files are always preserved.
- **`db`** — runs `migrate:fresh` (optionally `--seed`). **Excluded from
`all`.** Requires `--force` AND passes the production-safety
`confirmToProceed()` guard; it is a no-op under `--dry-run`.
- **`all`** — tidies cache + `logs` / `temp`, and `storage` **only when scoped**
(see below). It **never** runs the destructive `db` action.

### The `storage` action needs a scope

`storage` sweeps `storage/app/public`, `storage/app/uploads` and
`storage/app/exports`. The first is the disk behind `storage:link` — user
uploads. So unlike `logs` and `temp`, which hold data the application
regenerates, an unfiltered sweep here is not housekeeping.

| Invocation | Result |
|---|---|
| `tidy storage --days=30 --force` | Deletes uploads older than 30 days. |
| `tidy storage --size=100 --force` | Deletes uploads over 100 MB. |
| `tidy storage --force` | **Refused.** Every file would match. |
| `tidy storage --unfiltered --force` | Deletes everything — outside production only. |
| `tidy storage --unfiltered --force` *(production)* | **Refused**, with no override. |
| `tidy all --force` | Sweeps cache + logs + temp; **skips** `storage` and says so. |
| `tidy all --days=30 --force` | Sweeps everything, `storage` included. |

> Through v0.1.0, `tidy storage --force` and `tidy all --force` deleted every
> file in those roots. The containment guard did not catch it and never would
> have: it answers "can this delete something outside `storage_path()`", and
> those roots are inside it.

### Safety notes

- **Every deletion is confined to `storage_path()`.** The swept roots are
storage-relative; each is realpath-resolved and proven to sit inside
`realpath(storage_path())` before any delete. Each candidate file is
realpath-resolved and re-checked for containment, so a `..` path or a symlink
pointing **outside** storage is skipped, never followed. Paths are additionally
screened by the `FilePathGuard` (`..` / null-byte rejection).
- **`--dry-run` deletes nothing** — it previews each candidate and reports the
space that *would* be freed.
- **`db` is hard-gated** behind `--force` + `confirmToProceed()` and excluded
from `all`, so a bulk tidy can never drop your tables.
- **`--force` is not the gate for user files.** It appears in every CI
invocation, so it is typed by habit; scoping the `storage` sweep with
`--days`/`--size`, or acknowledging it with `--unfiltered`, is.
- **Signal-safe sweep.** The deletion loop polls
`signals()->shouldKeepRunning()` per root and per file, so a `SIGTERM` /
`SIGINT` stops the sweep cleanly mid-directory rather than leaving it half-run.
Without ext-pcntl (e.g. Windows) the check defaults `true`, so a normal run is
unaffected. Every run is timed and a `logger()->logCompletion()` summary records
files-processed and space-freed.

---

## See also
Expand Down
4 changes: 2 additions & 2 deletions docs/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ throw InvalidPathException::directoryTraversal($path); // \RuntimeException

`InvalidPathException` adds reason-specific factories on top of `create()`:
`directoryTraversal()`, `nullByteDetected()`, `outsideAllowedDirectory()`, and
`invalidCharacters()` — used by the path-confinement guards across the file,
database, and tidy code paths.
`invalidCharacters()` — used by the path-confinement guards across the file
and database code paths.

`FileTooLargeException::create()` formats both the actual and maximum size as
human-readable strings (e.g. `1.50 MB`) in the message.
Expand Down
3 changes: 0 additions & 3 deletions routes/api.php

This file was deleted.

57 changes: 0 additions & 57 deletions routes/web.php

This file was deleted.

Loading
Loading