Iteration 5: export / import commands
Implements the `export` and `import` app-level commands per ADR-0015 §11 + ADR-0007 amendment 1. - `export [<path>]` writes a zip of project.yaml + data/ to <data-root>/YYYYMMDD-<projectname>-export-NN.zip by default, preserving the project's directory name as the single top-level folder inside the archive. - `import <zip> [as <target>]` extracts an exported zip into a new named project and switches to it. Target name is derived from the zip's top-level folder by default; on collision the destination auto-suffixes -02, -03, ... up to -99 instead of refusing (deviates from §2's refuse-on- collision rule for save/save as; recorded as an amendment to ADR-0015 §11). - Excludes playground.db and history.log from the zip. - Path-traversal protection via zip::enclosed_name + post- resolution check that the extraction path stays inside the target directory. Adds the zip = "5" dep with default-features = false + features = ["deflate"] to keep the binary-size cost modest. Test baseline: 370 passing, 0 failing, 0 skipped.
This commit is contained in:
@@ -420,15 +420,38 @@ ADR-0003:
|
||||
- **`rebuild`** — section 7.
|
||||
- **`export`** — produces a zip per ADR-0007, *excluding*
|
||||
both `playground.db` and `history.log` (see ADR-0007
|
||||
amendment below). Default filename pattern unchanged.
|
||||
- **`import`** — accepts an exported zip, unpacks it into a
|
||||
named project at a chosen location, runs `rebuild` on
|
||||
open. The exported zip has no `playground.db` and no
|
||||
`history.log`, so a fresh `playground.db` is created from
|
||||
YAML+CSV, and `history.log` starts empty. The chosen
|
||||
target directory must not already exist (per the §2
|
||||
collision rule); the user picks a different name or
|
||||
removes the existing directory first.
|
||||
amendment below). The zip preserves the project's directory
|
||||
name as a single top-level folder (so unzipping creates
|
||||
`<projectname>/project.yaml` etc. rather than scattering
|
||||
files into the recipient's CWD). Default output is the
|
||||
active data root with the filename pattern
|
||||
`YYYYMMDD-<projectname>-export-NN.zip`, where `NN` is a
|
||||
two-digit zero-padded counter that skips taken slots in
|
||||
the same directory on the same day. `export <path>` overrides
|
||||
the default; relative `<path>` resolves under the active data
|
||||
root, absolute paths are used verbatim. Refuses if the final
|
||||
zip path already exists.
|
||||
- **`import`** — accepts an exported zip and switches to
|
||||
the resulting project. Grammar: `import <zip> [as <target>]`.
|
||||
The destination basename is taken from the zip's single
|
||||
top-level folder by default; an explicit `as <target>`
|
||||
overrides it. Relative `<target>` resolves under
|
||||
`<data-root>/projects/`; absolute paths are used verbatim.
|
||||
**Collision behaviour (amended)**: if the resolved
|
||||
destination already exists, `import` auto-suffixes the
|
||||
basename `-02`, `-03`, … up to `-99` to find a free slot,
|
||||
rather than refusing as the §2 collision rule prescribes
|
||||
for `save` / `save as`. Rationale: round-tripping zips
|
||||
between users (export → email → import → re-export → re-import)
|
||||
is a normal workflow, and forcing the recipient to type
|
||||
`as <target>` for every collision is unnecessary friction.
|
||||
Absolute `as <path>` is the user's explicit choice and is
|
||||
not auto-suffixed — the operation refuses on collision so
|
||||
the user gets exactly the path they asked for or a clear
|
||||
error. The exported zip has no `playground.db` and no
|
||||
`history.log`, so the imported project starts with neither;
|
||||
the runtime rebuilds `playground.db` from YAML+CSV on
|
||||
open, and `history.log` begins empty.
|
||||
|
||||
The `.gitignore` template (F2) is created in every new
|
||||
project directory and excludes:
|
||||
|
||||
Reference in New Issue
Block a user