valise-sh 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +152 -2
  2. package/README.md +99 -146
  3. package/bin/valise +57 -5376
  4. package/docs/architecture.md +77 -121
  5. package/docs/contrib-modules.md +172 -218
  6. package/docs/installation.md +137 -83
  7. package/docs/module-abi.md +125 -126
  8. package/docs/projects.md +249 -0
  9. package/docs/release-policy.md +68 -93
  10. package/docs/shared-modules.md +40 -39
  11. package/examples/README.md +12 -11
  12. package/examples/e2e/README.md +59 -66
  13. package/examples/e2e/bundles.tsv +6 -0
  14. package/examples/e2e/module.d/e2e_aliases.meta +1 -2
  15. package/examples/e2e/module.d/e2e_completions_bash.meta +0 -1
  16. package/examples/e2e/module.d/e2e_completions_zsh.meta +0 -1
  17. package/examples/e2e/module.d/e2e_env_setup.meta +2 -3
  18. package/examples/e2e/module.d/e2e_fish_features.meta +0 -1
  19. package/examples/e2e/module.d/e2e_generated_config.meta +1 -2
  20. package/examples/e2e/module.d/e2e_ksh_features.meta +0 -1
  21. package/examples/e2e/module.d/e2e_layer_probe.meta +1 -2
  22. package/examples/e2e/module.d/e2e_optional_network.meta +1 -2
  23. package/examples/e2e/module.d/e2e_platform_tools.meta +8 -0
  24. package/examples/e2e/module.d/e2e_platform_tools_bsd.meta +2 -3
  25. package/examples/e2e/module.d/e2e_platform_tools_darwin.meta +2 -3
  26. package/examples/e2e/module.d/e2e_platform_tools_linux.meta +2 -3
  27. package/examples/e2e/valise.conf +4 -0
  28. package/examples/e2e/variants.tsv +3 -0
  29. package/examples/local/module.d/hello_valise.meta +1 -2
  30. package/examples/profile-project/README.md +78 -108
  31. package/examples/profile-project/bundles.tsv +8 -0
  32. package/examples/profile-project/{shell/layers → layers}/demo/module.d/contrib_demo_greeting.meta +1 -2
  33. package/examples/profile-project/{shell/module.d → module.d}/greeting.meta +1 -2
  34. package/examples/profile-project/{shell/module.d → module.d}/profile_main.meta +0 -1
  35. package/examples/profile-project/{shell/modules → modules}/greeting.sh +1 -1
  36. package/examples/profile-project/valise.conf +4 -0
  37. package/examples/profile-project/variants.tsv +3 -0
  38. package/examples/shared/README.md +13 -34
  39. package/examples/shared/module.d/contrib_shared_paths.meta +2 -3
  40. package/examples/shared/module.d/contrib_shared_umask.meta +1 -2
  41. package/examples/shared/modules/shared/contrib_shared_paths.sh +2 -2
  42. package/lib/capabilities.sh +40 -13
  43. package/lib/cli/commands.sh +278 -0
  44. package/lib/cli/common.sh +326 -0
  45. package/lib/cli/devtools.sh +539 -0
  46. package/lib/cli/fish-hook.sh +986 -0
  47. package/lib/cli/integration.sh +337 -0
  48. package/lib/cli/lifecycle.sh +578 -0
  49. package/lib/cli/project.sh +1807 -0
  50. package/lib/cli/store.sh +1023 -0
  51. package/lib/cli/usage.sh +308 -0
  52. package/lib/compiler.awk +11 -4
  53. package/lib/fish/loader.fish +69 -9
  54. package/lib/fish/metadata.fish +49 -5
  55. package/lib/fish/profile.fish +170 -0
  56. package/lib/loader.sh +6 -8
  57. package/lib/metadata.sh +111 -5
  58. package/manifests/parser-support.tsv +2 -2
  59. package/module.d/valise_editor.meta +9 -0
  60. package/module.d/valise_history.meta +9 -0
  61. package/module.d/valise_pager.meta +9 -0
  62. package/module.d/valise_path.meta +9 -0
  63. package/module.d/{platform_bsd.meta → valise_platform_bsd.meta} +3 -4
  64. package/module.d/{platform_linux.meta → valise_platform_linux.meta} +3 -4
  65. package/module.d/{platform_macos.meta → valise_platform_macos.meta} +3 -4
  66. package/module.d/{platform_posix.meta → valise_platform_posix.meta} +2 -3
  67. package/module.d/valise_xdg.meta +9 -0
  68. package/modules/system/{contrib_editor.sh → valise_editor.sh} +2 -2
  69. package/modules/system/{contrib_history.sh → valise_history.sh} +2 -2
  70. package/modules/system/{contrib_pager.sh → valise_pager.sh} +2 -2
  71. package/modules/system/{contrib_path.sh → valise_path.sh} +3 -3
  72. package/modules/system/{contrib_xdg.sh → valise_xdg.sh} +2 -2
  73. package/package.json +91 -10
  74. package/docs/variants.md +0 -327
  75. package/entrypoints/.gitkeep +0 -0
  76. package/entrypoints/fish/profile.fish +0 -99
  77. package/examples/e2e/build-manifest.sh +0 -138
  78. package/examples/local/.gitkeep +0 -0
  79. package/examples/profile-project/shell/release/bundle-roots +0 -9
  80. package/examples/profile-project/shell/release/install-variants +0 -3
  81. package/examples/profile-project/shell/release/module-registry.tsv +0 -4
  82. package/examples/profile-project/shell/release/project.conf +0 -3
  83. package/lib/.gitkeep +0 -0
  84. package/manifests/.gitkeep +0 -0
  85. package/module.d/.gitkeep +0 -0
  86. package/module.d/contrib_editor.meta +0 -10
  87. package/module.d/contrib_history.meta +0 -10
  88. package/module.d/contrib_pager.meta +0 -10
  89. package/module.d/contrib_path.meta +0 -10
  90. package/module.d/contrib_xdg.meta +0 -10
  91. package/modules/.gitkeep +0 -0
  92. /package/examples/profile-project/{shell/layers → layers}/demo/modules/shared/contrib_demo_greeting.sh +0 -0
@@ -1,10 +1,8 @@
1
1
  # Installation and releases
2
2
 
3
- `bin/valise` is a `/bin/sh` CLI. Its normal install and verification commands
4
- need the platform utilities used by the release manifest and integrity checks
5
- (for example, a SHA-256 utility). Every documented command runs without
6
- Python 3. `explain-bundles` reads the generated registry and the bundle
7
- roots; `layer-lint` reads layer metadata directly.
3
+ `bin/valise` is a `/bin/sh` CLI. It needs the standard POSIX utilities and a
4
+ SHA-256 utility (`sha256sum`, `shasum`, or `sha256`); no command needs
5
+ Python, Perl, or another runtime.
8
6
 
9
7
  ## Install the CLI
10
8
 
@@ -53,30 +51,39 @@ file shadows it. It then adds the files each shell reads instead:
53
51
  - `${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/valise.fish` when valise
54
52
  finds a Fish executable in a trusted location.
55
53
 
56
- `--no-integration` writes no startup file and no Fish hook. `valise apply`
54
+ `--no-integration` writes no startup file, no Fish hook, and no command
55
+ stubs. `valise apply`
57
56
  builds, installs, and then uses the same writer.
58
57
 
59
58
  Each POSIX file receives a managed block between `# BEGIN valise managed
60
59
  block` and `# END valise managed block`. Existing content is preserved, and
61
- a repeat install replaces the block in place. An unpaired marker is refused
62
- and stops the install; a file already processed keeps its updated block. A
63
- symlinked POSIX startup file is reported and left untouched. If the Fish
64
- hook path is a symlink or a directory, install fails rather than write
65
- through it. Install prints every path it wrote and how to remove the
66
- integration.
67
-
68
- The POSIX snippet adapts to the release shape:
69
-
70
- - A project release built by `valise build` carries
71
- `generated/installed-modules.<shell>` for each shell its bundle seeds
72
- (`bash`, `zsh`, and `ksh` for the scaffold and the example). The snippet
73
- sources the plan for the running shell.
74
- - An engine release carries `entrypoints/profile.sh` and the loader. The
75
- snippet sources that entrypoint.
76
- - If the release has neither a plan for the running shell nor the loader,
77
- the snippet leaves the session alone. This is the result for a login
78
- `dash` session with a built release: `dash` has no plan, and a built
79
- release has no loader.
60
+ a repeat install replaces the block in place. A new block is appended with
61
+ nothing added around it, so removing the block restores the file. When the
62
+ file's last line has no newline, install adds one before the block and a note
63
+ line inside it, and uninstall removes both. Rows or lines after the block
64
+ keep the file's own ending, newline or not, and a repeat install leaves them
65
+ alone. A symlinked POSIX startup file is reported and left untouched.
66
+
67
+ Install checks every startup file, the Fish hook path, and the command
68
+ directory before it changes the active release. The command directory is
69
+ checked only when the release publishes commands or a command ledger exists.
70
+ An unpaired marker, a startup path that is not a regular file, a directory
71
+ install cannot write, a command directory it cannot write, or a Fish hook
72
+ path that is a symlink or a directory stops the install with nothing changed. If a later step fails
73
+ anyway, install puts the previous active release back (or removes the active
74
+ link when there was none) and names the files it had already written. Those
75
+ files load whichever release is active, so they need no repair.
76
+
77
+ Install also warns about lines outside a managed block that load valise by
78
+ hand: a reference to `valise/active`, a 0.1 `installed-modules.` list, or a
79
+ line that sources a compiled plan. Such a line loads the profile a second
80
+ time; remove it. `valise doctor` reports the same lines as a failure.
81
+
82
+ The block detects the running shell class, `ksh`, `zsh`, `bash`, or `sh` for
83
+ any other POSIX shell, and sources `plans/<class>.sh` from the active
84
+ release. It sets `_valise_profile_loaded` only when the plan succeeds, so a
85
+ later startup file in the same session retries after a failure.
86
+ `VALISE_RELOAD=1` makes a later block load the profile again.
80
87
 
81
88
  `valise install` writes `~/.profile`, which ksh reads at login. A non-login
82
89
  interactive ksh reads the file named by `$ENV` when that variable is set.
@@ -87,16 +94,33 @@ Zsh does not read `~/.profile` on its own: zsh login shells read
87
94
  `.zprofile`, not `~/.profile`. Install writes the same managed block to
88
95
  `~/.zprofile`, so zsh needs no separate pointer.
89
96
 
90
- A built project release has no Fish surface: no Fish entrypoint, generated
91
- Fish plan, or native Fish loader. The Fish hook verifies the active release,
92
- then loads nothing and prints nothing. Only an engine release with
93
- `entrypoints/fish/profile.fish` uses the native Fish loader.
97
+ When the bundle has `fish` rows, the release carries `runtime/fish/`: the
98
+ native Fish metadata reader and loader and `runtime/fish/profile.fish`, which
99
+ loads the bundle's Fish roots recorded in `valise-release.conf`. A release
100
+ whose bundle has no Fish rows has no `runtime/fish/`; the Fish hook then loads
101
+ nothing and prints nothing.
94
102
 
95
103
  The Fish hook verifies the active release before loading it. The check
96
104
  validates the active pointer, the release manifest, and every listed file.
97
- If verification fails, the hook loads no release code. Fish continues
98
- starting even if a `conf.d` hook fails, so this does not abort the
99
- interactive shell.
105
+ If verification fails, the hook loads no release code and reports
106
+ `active release authentication failed`. Fish continues starting even if a
107
+ `conf.d` hook fails, so this does not abort the interactive shell.
108
+
109
+ The Fish hook is several hundred lines while the POSIX block is about twenty.
110
+ The difference is deliberate. The POSIX block sources a plan and trusts the
111
+ release on disk, as any startup file does. The Fish hook does more because
112
+ it must not trust release code before that code is verified, and Fish gives a
113
+ sourced file the caller's global functions and variables. The hook therefore
114
+ carries its own verification and cleanup, written before any release file
115
+ runs: it authenticates the release through `valise release verify`, saves the
116
+ caller's `SHELL_PROJECT_ROOT` and `VALISE_MODULE_ROOT` in every scope, records
117
+ every caller function so a release cannot silently replace one, sources
118
+ `runtime/fish/profile.fish` inside that transaction, and on any failure
119
+ removes what the release defined and restores the saved state. None of this
120
+ can live in the release, because a tampered release would carry tampered
121
+ checks. `valise apply` compares the generated hook with the installed one
122
+ byte for byte and reports `Fish hook current` without rewriting an identical
123
+ hook.
100
124
 
101
125
  To write integration by hand, print it once and paste or save it:
102
126
 
@@ -106,8 +130,8 @@ valise init posix # POSIX managed block
106
130
  valise init fish # Fish conf.d hook; save it as fish/conf.d/valise.fish
107
131
  ```
108
132
 
109
- Evaluating `valise init` output directly at every prompt —
110
- `eval "$(valise init)"` — re-runs CLI startup code in every shell and is
133
+ Evaluating `valise init` output at every prompt, as in
134
+ `eval "$(valise init)"`, re-runs CLI startup code in every shell and is
111
135
  not the routine path; prefer `valise install` or `valise apply`, which
112
136
  write the files once.
113
137
 
@@ -137,42 +161,55 @@ as an installed shell hook.
137
161
 
138
162
  ## Build a release
139
163
 
140
- A release tree contains the engine runtime, module metadata and bodies, and
141
- any profile-specific files. `valise make-manifest` writes a manifest into the
142
- tree:
164
+ `valise build` turns a project into a release tree (see
165
+ [projects.md](projects.md#valise-build)), and `valise apply` builds and
166
+ installs it in one step. `valise install` installs the tree in the current
167
+ directory, so a tree built elsewhere installs the same way:
143
168
 
144
169
  ```sh
145
- valise make-manifest ./mytree
146
- ```
147
-
148
- By default this creates
149
- `./mytree/shell/install/profile-release.manifest`. Use `--output <file>` to
150
- choose another manifest path. The manifest records each regular file's size
151
- and SHA-256 digest; source and destination paths are the same. The tree must
152
- contain files with mode `0644` or `0755`. Symlinks, unsupported path
153
- characters, and other file modes are rejected. Rebuilding an unchanged tree
154
- produces the same manifest.
155
-
156
- Install and verify from the tree root:
157
-
158
- ```sh
159
- cd mytree
170
+ cd build/release
160
171
  valise install
161
172
  valise release verify
162
173
  ```
163
174
 
164
- Install stages a sealed, read-only copy under the XDG data directory and
165
- updates the active symlink. `release verify` checks the installed bytes against
166
- their manifest. Fish repeats that check at startup; the POSIX startup snippets
167
- do not, so run `release verify` separately if you need a check before sourcing
168
- in a POSIX shell.
169
-
170
- `valise build` builds a consuming project's release tree: the module
171
- registry, the per-shell plans, every module body, and the manifest inputs.
172
- The engine itself ships as an engine release rather than a built project
173
- release; it carries `entrypoints/profile.sh` and `lib/`, not `generated/`.
174
- The included [e2e example](../examples/e2e/README.md) demonstrates a small
175
- complete profile.
175
+ Install requires `manifest.tsv`, which lists every regular file with its
176
+ size and SHA-256 digest, and a `valise-release.conf` declaring `format=2`;
177
+ a tree from an older valise must be rebuilt. It copies the tree into
178
+ the release store under the manifest's SHA-256, makes the copy read-only,
179
+ verifies it, and switches the active link. `release verify` checks the
180
+ installed bytes against their manifest again. Fish repeats that check at
181
+ every startup; the POSIX block does not, so run `release verify` when you
182
+ want a check before a POSIX shell sources the release.
183
+
184
+ `valise make-manifest <tree> [--output <file>]` writes the manifest for a
185
+ tree; `valise build` uses it. Files must have mode `0644` or `0755`;
186
+ symlinks, unsupported path characters, and other modes are rejected. An
187
+ unchanged tree always produces the same manifest.
188
+
189
+ ## Published commands
190
+
191
+ A release whose modules declare `commands=` or `executables=` carries a
192
+ `bin/` directory (see [the module ABI](module-abi.md#published-commands)).
193
+ `valise install` writes a stub for each name into the command directory,
194
+ `${VALISE_BIN_DIR:-${XDG_BIN_HOME:-$HOME/.local/bin}}`. A stub is a short
195
+ `sh` script that runs the same name under the active release link, so
196
+ switching releases never rewrites a stub; install and `valise release use`
197
+ add the stubs the new release needs and remove the ones it no longer has.
198
+
199
+ valise records each stub it writes, with its SHA-256, in
200
+ `$XDG_STATE_HOME/valise/commands.tsv` before writing the stub, so an
201
+ interrupted install never leaves a stub valise does not know about. It only
202
+ touches files listed there, and it refuses a release whose `commands=` holds
203
+ a name the build would have rejected:
204
+
205
+ - A file valise did not write is never replaced. Install reports the
206
+ command as not published, and `valise doctor` reports the collision.
207
+ - A stub that was edited after valise wrote it is left in place and
208
+ dropped from the ledger.
209
+
210
+ `valise install --no-integration` writes no stubs and no ledger, and a later
211
+ `valise release use` leaves the command directory alone. Install and
212
+ `valise doctor` warn when the command directory is not on PATH.
176
213
 
177
214
  ## Integrity and provenance
178
215
 
@@ -194,18 +231,33 @@ controls.
194
231
  | Releases | `~/.local/share/valise/releases/` | `$XDG_DATA_HOME/valise/releases/` |
195
232
  | Active release | `~/.config/valise/active` | `$XDG_CONFIG_HOME/valise/active` |
196
233
  | Lock and state | `~/.local/state/valise/` | `$XDG_STATE_HOME/valise/` |
234
+ | Command stubs | `~/.local/bin/` | `$VALISE_BIN_DIR`, then `$XDG_BIN_HOME` |
197
235
 
198
- The active path is a symlink to the selected release.
236
+ The active path is a symlink to the selected release. The state directory
237
+ holds the lock, the previous-release record (`previous`), the command-stub
238
+ ledger (`commands.tsv`), and the list of startup files install created
239
+ (`startup-created`). Uninstall uses the same locations, including any XDG
240
+ overrides.
199
241
 
200
242
  ## Managing installed releases
201
243
 
202
244
  `valise release list` shows the installed releases and marks the active one.
203
- `valise release use <release-id>` points the active symlink at an existing
204
- release.
245
+ `valise release use <release-id>` verifies an installed release and points
246
+ the active symlink at it. Each time the active release changes, valise
247
+ records the one it replaced in `$XDG_STATE_HOME/valise/previous`;
248
+ `valise release rollback` verifies that release and makes it active again, so
249
+ two rollbacks in a row return to where they started. Both move the command
250
+ stubs to the new active release when install published them.
251
+
252
+ `valise apply --dry-run` builds the project and prints what install would
253
+ do: whether the release is installed or active already, and for each startup
254
+ file, the Fish hook, and each command stub whether it is current or would be
255
+ created, updated, published, skipped, or removed. It writes nothing outside
256
+ `build/`.
205
257
 
206
258
  `valise release prune --keep=<n>` removes old releases. It always keeps the
207
259
  active release and the `n` newest others, and it never removes a release that
208
- fails verification against its own manifest — a release that cannot be
260
+ fails verification against its own manifest. A release that cannot be
209
261
  verified is reported and kept, so a damaged tree is not silently discarded.
210
262
  The default is `--keep=3`.
211
263
 
@@ -223,26 +275,28 @@ valise install --help
223
275
 
224
276
  ## Updating and removing
225
277
 
226
- Update a source checkout with Git. To update an npm installation, repack and
227
- reinstall the tarball. `valise install` updates the active profile release;
278
+ Update a source checkout with Git. To update a package installation, repack
279
+ and reinstall the tarball. `valise install` updates the active profile release;
228
280
  it does not update the CLI itself.
229
281
 
230
- There is no `valise uninstall` command. Before removing the CLI, remove the
231
- managed integration from every startup file valise wrote: delete only the
232
- block between `# BEGIN valise managed block` and `# END valise managed
233
- block` in `~/.profile` and in any of `~/.bash_profile`, `~/.bash_login`,
234
- `~/.bashrc`, `~/.zshrc`, and `~/.zprofile`. For Fish, remove
235
- `${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/valise.fish` if it is still
236
- the valise-managed hook. Then uninstall the CLI and remove the valise XDG
237
- state. Installed release files are read-only, so make them writable first:
282
+ `valise uninstall` removes what install wrote:
283
+
284
+ - the managed block from `~/.profile`, `~/.bash_profile`, `~/.bash_login`,
285
+ `~/.bashrc`, `~/.zprofile`, and `~/.zshrc`, keeping every other byte and
286
+ the file mode; a file install created is deleted when nothing else is left
287
+ in it
288
+ - the Fish hook, when its first line is the valise header
289
+ - the command stubs valise owns, except a stub that was edited after valise
290
+ wrote it
291
+ - the active link, the previous-release record, and the installed releases
292
+
293
+ It checks every startup file for paired markers before it changes anything.
294
+ A symlinked startup file and lines that load valise outside a managed block
295
+ are reported for you to edit. `--keep-releases` keeps the installed releases.
296
+ Then remove the CLI:
238
297
 
239
298
  ```sh
240
- chmod -R u+w "$HOME/.local/share/valise" 2>/dev/null || :
241
- rm -rf "$HOME/.local/share/valise" "$HOME/.config/valise" "$HOME/.local/state/valise"
242
299
  # Run only if installed from npm:
243
300
  npm uninstall -g valise-sh
244
301
  # For a source install, remove bin/ from PATH and delete any symlink you created.
245
302
  ```
246
-
247
- If you use non-default XDG directories, remove the corresponding `valise/`
248
- subdirectories there instead.