valise-sh 0.1.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 (88) hide show
  1. package/CHANGELOG.md +320 -9
  2. package/README.md +104 -127
  3. package/bin/valise +67 -3266
  4. package/docs/architecture.md +76 -51
  5. package/docs/contrib-modules.md +180 -173
  6. package/docs/installation.md +210 -69
  7. package/docs/module-abi.md +211 -148
  8. package/docs/projects.md +249 -0
  9. package/docs/release-policy.md +90 -11
  10. package/docs/shared-modules.md +149 -26
  11. package/examples/README.md +20 -8
  12. package/examples/e2e/README.md +61 -68
  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 +151 -0
  31. package/examples/profile-project/bundles.tsv +8 -0
  32. package/examples/profile-project/layers/demo/module.d/contrib_demo_greeting.meta +9 -0
  33. package/examples/profile-project/layers/demo/modules/shared/contrib_demo_greeting.sh +4 -0
  34. package/examples/profile-project/module.d/greeting.meta +9 -0
  35. package/examples/profile-project/module.d/profile_main.meta +8 -0
  36. package/examples/profile-project/modules/greeting.sh +5 -0
  37. package/examples/profile-project/valise.conf +4 -0
  38. package/examples/profile-project/variants.tsv +3 -0
  39. package/examples/shared/README.md +13 -34
  40. package/examples/shared/module.d/contrib_shared_paths.meta +2 -3
  41. package/examples/shared/module.d/contrib_shared_umask.meta +1 -2
  42. package/examples/shared/modules/shared/contrib_shared_paths.sh +2 -2
  43. package/lib/capabilities.sh +40 -13
  44. package/lib/cli/commands.sh +278 -0
  45. package/lib/cli/common.sh +326 -0
  46. package/lib/cli/devtools.sh +539 -0
  47. package/lib/cli/fish-hook.sh +986 -0
  48. package/lib/cli/integration.sh +337 -0
  49. package/lib/cli/lifecycle.sh +578 -0
  50. package/lib/cli/project.sh +1807 -0
  51. package/lib/cli/store.sh +1023 -0
  52. package/lib/cli/usage.sh +308 -0
  53. package/lib/compiler.awk +352 -24
  54. package/lib/fish/loader.fish +163 -28
  55. package/lib/fish/metadata.fish +62 -19
  56. package/lib/fish/profile.fish +170 -0
  57. package/lib/loader.sh +736 -158
  58. package/lib/metadata.sh +137 -55
  59. package/manifests/parser-support.tsv +2 -2
  60. package/module.d/valise_editor.meta +9 -0
  61. package/module.d/valise_history.meta +9 -0
  62. package/module.d/valise_pager.meta +9 -0
  63. package/module.d/valise_path.meta +9 -0
  64. package/module.d/{platform_bsd.meta → valise_platform_bsd.meta} +3 -4
  65. package/module.d/{platform_linux.meta → valise_platform_linux.meta} +3 -4
  66. package/module.d/{platform_macos.meta → valise_platform_macos.meta} +3 -4
  67. package/module.d/{platform_posix.meta → valise_platform_posix.meta} +2 -3
  68. package/module.d/valise_xdg.meta +9 -0
  69. package/modules/system/{contrib_editor.sh → valise_editor.sh} +2 -2
  70. package/modules/system/{contrib_history.sh → valise_history.sh} +2 -2
  71. package/modules/system/{contrib_pager.sh → valise_pager.sh} +2 -2
  72. package/modules/system/{contrib_path.sh → valise_path.sh} +3 -3
  73. package/modules/system/{contrib_xdg.sh → valise_xdg.sh} +2 -2
  74. package/package.json +91 -9
  75. package/docs/variants.md +0 -63
  76. package/entrypoints/.gitkeep +0 -0
  77. package/entrypoints/fish/profile.fish +0 -99
  78. package/examples/e2e/build-manifest.sh +0 -250
  79. package/examples/local/.gitkeep +0 -0
  80. package/lib/.gitkeep +0 -0
  81. package/manifests/.gitkeep +0 -0
  82. package/module.d/.gitkeep +0 -0
  83. package/module.d/contrib_editor.meta +0 -10
  84. package/module.d/contrib_history.meta +0 -10
  85. package/module.d/contrib_pager.meta +0 -10
  86. package/module.d/contrib_path.meta +0 -10
  87. package/module.d/contrib_xdg.meta +0 -10
  88. package/modules/.gitkeep +0 -0
@@ -1,9 +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). The consuming-project commands `compile`,
6
- `explain-variant`, `explain-bundles`, and `layer-lint` also require Python 3.
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.
7
6
 
8
7
  ## Install the CLI
9
8
 
@@ -38,38 +37,122 @@ packed CLI without a registry, and loads a module from the extracted package.
38
37
  ## Shell integration
39
38
 
40
39
  `valise install` installs the release described by the manifest in the
41
- current project and deploys shell integration. It adds a POSIX snippet for
42
- bash, zsh, or ksh when that shell is named by `$SHELL`, and a Fish `conf.d`
43
- hook when it finds Fish in a trusted system path. On macOS the POSIX snippet
44
- goes to `.bash_profile`, `.zprofile`, or `.kshrc`; on other systems it goes to
45
- `.bashrc`, `.zshrc`, or `.kshrc`. The Fish hook is
46
- `${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/valise.fish`. Existing POSIX
47
- profile content is preserved; a marker makes repeat installs idempotent.
48
- Symlinked POSIX profile files are left alone.
49
-
50
- To print a POSIX integration snippet without editing a startup file:
40
+ current directory and writes shell integration by default. It always writes
41
+ `~/.profile`, which login `sh`, `ksh`, and bash sessions read when no other
42
+ file shadows it. It then adds the files each shell reads instead:
43
+
44
+ - `~/.bash_profile` or `~/.bash_login` when one exists, because login bash
45
+ reads the first of those instead of `~/.profile`. valise never creates
46
+ either file.
47
+ - `~/.bashrc` when bash is `$SHELL` or the file already exists.
48
+ - `~/.zprofile` when zsh is on `PATH` or the file already exists. It is the
49
+ login file zsh reads instead of `~/.profile`.
50
+ - `~/.zshrc` when zsh is `$SHELL` or the file already exists.
51
+ - `${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/valise.fish` when valise
52
+ finds a Fish executable in a trusted location.
53
+
54
+ `--no-integration` writes no startup file, no Fish hook, and no command
55
+ stubs. `valise apply`
56
+ builds, installs, and then uses the same writer.
57
+
58
+ Each POSIX file receives a managed block between `# BEGIN valise managed
59
+ block` and `# END valise managed block`. Existing content is preserved, and
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.
87
+
88
+ `valise install` writes `~/.profile`, which ksh reads at login. A non-login
89
+ interactive ksh reads the file named by `$ENV` when that variable is set.
90
+ valise does not choose or edit that file; to cover non-login ksh, put the
91
+ POSIX block in the file `$ENV` names.
92
+
93
+ Zsh does not read `~/.profile` on its own: zsh login shells read
94
+ `.zprofile`, not `~/.profile`. Install writes the same managed block to
95
+ `~/.zprofile`, so zsh needs no separate pointer.
96
+
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.
102
+
103
+ The Fish hook verifies the active release before loading it. The check
104
+ validates the active pointer, the release manifest, and every listed file.
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.
124
+
125
+ To write integration by hand, print it once and paste or save it:
51
126
 
52
127
  ```sh
53
- eval "$(valise init)"
128
+ valise init # POSIX block, or the Fish hook when $SHELL is fish
129
+ valise init posix # POSIX managed block
130
+ valise init fish # Fish conf.d hook; save it as fish/conf.d/valise.fish
54
131
  ```
55
132
 
56
- For Fish, source the generated integration in `~/.config/fish/config.fish`:
133
+ Evaluating `valise init` output at every prompt, as in
134
+ `eval "$(valise init)"`, re-runs CLI startup code in every shell and is
135
+ not the routine path; prefer `valise install` or `valise apply`, which
136
+ write the files once.
57
137
 
58
- ```fish
59
- valise init fish | source
60
- ```
138
+ `valise doctor` verifies the active release against its own manifest, checks
139
+ the startup files the running shell actually reads for valise integration,
140
+ including the `.bash_profile` or `.bash_login` that bash reads instead of
141
+ `~/.profile`. It exits nonzero when the active release is missing or fails
142
+ verification, and when no startup file carries the integration.
143
+
144
+ Two options adjust its scope:
61
145
 
62
- The Fish integration verifies the active release before loading it. The
63
- check validates the active pointer, the release manifest, and every listed
64
- file. If verification fails, the hook loads no release code. Fish continues
65
- starting even if a `conf.d` hook fails, so this does not abort the interactive
66
- shell.
146
+ - `--pre-install` skips the active-release and startup-file checks, so doctor
147
+ can run before the first install.
148
+ - `--no-integration` skips the startup-file check and reports only the
149
+ release.
67
150
 
68
- `valise verify-active` runs the release verification without sourcing shell
151
+ `valise release verify` runs the release verification without sourcing shell
69
152
  code:
70
153
 
71
154
  ```sh
72
- valise verify-active
155
+ valise release verify
73
156
  ```
74
157
 
75
158
  The development-only `valise init fish --source-tree` mode skips installed
@@ -78,39 +161,55 @@ as an installed shell hook.
78
161
 
79
162
  ## Build a release
80
163
 
81
- A release tree contains the engine runtime, module metadata and bodies, and
82
- any profile-specific files. `valise make-manifest` writes a manifest into the
83
- 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:
84
168
 
85
169
  ```sh
86
- valise make-manifest ./mytree
87
- ```
88
-
89
- By default this creates
90
- `./mytree/shell/install/profile-release.manifest`. Use `--output <file>` to
91
- choose another manifest path. The manifest records each regular file's size
92
- and SHA-256 digest; source and destination paths are the same. The tree must
93
- contain files with mode `0644` or `0755`. Symlinks, unsupported path
94
- characters, and other file modes are rejected. Rebuilding an unchanged tree
95
- produces the same manifest.
96
-
97
- Install and verify from the tree root:
98
-
99
- ```sh
100
- cd mytree
170
+ cd build/release
101
171
  valise install
102
- valise verify-active
172
+ valise release verify
103
173
  ```
104
174
 
105
- Install stages a sealed, read-only copy under the XDG data directory and
106
- updates the active symlink. `verify-active` checks the installed bytes against
107
- their manifest. Fish repeats that check at startup; the POSIX startup snippets
108
- do not, so run `verify-active` separately if you need a check before sourcing
109
- in a POSIX shell.
110
-
111
- The engine does not build a consuming project's module catalog or release
112
- tree. The included [e2e example](../examples/e2e/README.md) demonstrates a
113
- small 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.
114
213
 
115
214
  ## Integrity and provenance
116
215
 
@@ -132,30 +231,72 @@ controls.
132
231
  | Releases | `~/.local/share/valise/releases/` | `$XDG_DATA_HOME/valise/releases/` |
133
232
  | Active release | `~/.config/valise/active` | `$XDG_CONFIG_HOME/valise/active` |
134
233
  | Lock and state | `~/.local/state/valise/` | `$XDG_STATE_HOME/valise/` |
234
+ | Command stubs | `~/.local/bin/` | `$VALISE_BIN_DIR`, then `$XDG_BIN_HOME` |
235
+
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.
241
+
242
+ ## Managing installed releases
135
243
 
136
- The active path is a symlink to the selected release.
244
+ `valise release list` shows the installed releases and marks the active one.
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/`.
257
+
258
+ `valise release prune --keep=<n>` removes old releases. It always keeps the
259
+ active release and the `n` newest others, and it never removes a release that
260
+ fails verification against its own manifest. A release that cannot be
261
+ verified is reported and kept, so a damaged tree is not silently discarded.
262
+ The default is `--keep=3`.
263
+
264
+ ## Command usage
265
+
266
+ Every command prints its own contract:
267
+
268
+ ```sh
269
+ valise compile --help
270
+ valise build --help
271
+ valise install --help
272
+ ```
273
+
274
+ `valise help` prints the global command list.
137
275
 
138
276
  ## Updating and removing
139
277
 
140
- Update a source checkout with Git. To update an npm installation, repack and
141
- 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;
142
280
  it does not update the CLI itself.
143
281
 
144
- There is no `valise uninstall` command. Before removing the CLI, remove the
145
- managed integration from your shell startup file: in POSIX files, delete only
146
- the block beginning `# valise shell integration — managed by valise install`;
147
- for Fish, remove
148
- `${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/valise.fish` if it is still
149
- the valise-managed hook. Then uninstall the CLI and remove the valise XDG
150
- 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:
151
297
 
152
298
  ```sh
153
- chmod -R u+w "$HOME/.local/share/valise" 2>/dev/null || :
154
- rm -rf "$HOME/.local/share/valise" "$HOME/.config/valise" "$HOME/.local/state/valise"
155
299
  # Run only if installed from npm:
156
300
  npm uninstall -g valise-sh
157
301
  # For a source install, remove bin/ from PATH and delete any symlink you created.
158
302
  ```
159
-
160
- If you use non-default XDG directories, remove the corresponding `valise/`
161
- subdirectories there instead.