valise-sh 0.1.0 → 0.2.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.
- package/CHANGELOG.md +170 -9
- package/README.md +48 -24
- package/bin/valise +2453 -333
- package/docs/architecture.md +78 -9
- package/docs/contrib-modules.md +68 -15
- package/docs/installation.md +120 -33
- package/docs/module-abi.md +103 -39
- package/docs/release-policy.md +110 -6
- package/docs/shared-modules.md +124 -2
- package/docs/variants.md +318 -54
- package/examples/README.md +11 -0
- package/examples/e2e/README.md +7 -7
- package/examples/e2e/build-manifest.sh +45 -157
- package/examples/profile-project/README.md +181 -0
- package/examples/profile-project/shell/layers/demo/module.d/contrib_demo_greeting.meta +10 -0
- package/examples/profile-project/shell/layers/demo/modules/shared/contrib_demo_greeting.sh +4 -0
- package/examples/profile-project/shell/module.d/greeting.meta +10 -0
- package/examples/profile-project/shell/module.d/profile_main.meta +9 -0
- package/examples/profile-project/shell/modules/greeting.sh +5 -0
- package/examples/profile-project/shell/release/bundle-roots +9 -0
- package/examples/profile-project/shell/release/install-variants +3 -0
- package/examples/profile-project/shell/release/module-registry.tsv +4 -0
- package/examples/profile-project/shell/release/project.conf +3 -0
- package/examples/shared/README.md +2 -2
- package/lib/compiler.awk +341 -20
- package/lib/fish/loader.fish +94 -19
- package/lib/fish/metadata.fish +13 -14
- package/lib/loader.sh +737 -157
- package/lib/metadata.sh +27 -51
- package/package.json +2 -1
package/docs/module-abi.md
CHANGED
|
@@ -19,7 +19,8 @@ Required fields:
|
|
|
19
19
|
| `shells` | Space-separated supported shell identifiers. |
|
|
20
20
|
| `platforms` | Space-separated supported platform identifiers. |
|
|
21
21
|
| `side_effect` | Startup/install/update side-effect class. |
|
|
22
|
-
| `load`
|
|
22
|
+
| `load` | Repository-relative path to the module body. |
|
|
23
|
+
| `requires` | Required when the module is a **meta module** (see below). |
|
|
23
24
|
|
|
24
25
|
Optional fields:
|
|
25
26
|
|
|
@@ -31,6 +32,37 @@ Optional fields:
|
|
|
31
32
|
| `updates` | Space-separated update identifiers registered by the consuming project. |
|
|
32
33
|
| `version` | Optional module version used to reject version skew across roots. |
|
|
33
34
|
|
|
35
|
+
## Meta modules
|
|
36
|
+
|
|
37
|
+
A **meta module** declares dependencies and has no body. It is the natural
|
|
38
|
+
way to write an aggregator: instead of an empty shell file whose only job is
|
|
39
|
+
to name a dependency, the aggregator is metadata alone.
|
|
40
|
+
|
|
41
|
+
A meta module has `requires=` with at least one entry and **no** `load=`.
|
|
42
|
+
The loader and the compiler treat it as a dependency-only
|
|
43
|
+
record: the module itself has nothing to source, but its required closure
|
|
44
|
+
loads normally and in the right order. A meta module must leave `exports=`
|
|
45
|
+
empty; it has no body to define an exported function. Metadata that declares
|
|
46
|
+
an export on a meta module is rejected.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
name=profile_main
|
|
50
|
+
version=1.0.0
|
|
51
|
+
side_effect=read-only
|
|
52
|
+
requires=contrib_path contrib_editor
|
|
53
|
+
optional=
|
|
54
|
+
shells=bash zsh ksh
|
|
55
|
+
platforms=any
|
|
56
|
+
exports=
|
|
57
|
+
updates=
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
At least one of `load` or `requires` is required: a module with neither a
|
|
61
|
+
body nor dependencies is malformed.
|
|
62
|
+
|
|
63
|
+
`load` and `requires` are **not** mutually exclusive. A module that declares
|
|
64
|
+
both loads its own body *and* its dependencies, in dependency-first order.
|
|
65
|
+
|
|
34
66
|
## Identifiers and allowed values
|
|
35
67
|
|
|
36
68
|
Module names, dependency names, and exported shell functions must match
|
|
@@ -41,13 +73,22 @@ non-empty list containing only whitespace is malformed. Required `shells` and
|
|
|
41
73
|
`platforms` lists must contain at least one parsed value. Empty identifiers,
|
|
42
74
|
whitespace inside one identifier, path separators, quotes, shell metacharacters,
|
|
43
75
|
glob characters, and control characters are invalid. Invalid identifiers are
|
|
44
|
-
rejected before the loader can build function names or
|
|
45
|
-
bridge hooks.
|
|
76
|
+
rejected before the loader can build function names or source a body.
|
|
46
77
|
|
|
47
78
|
Accepted `shells` values are `sh`, `bash`, `zsh`, `ksh`, and `fish`.
|
|
48
|
-
`shells=sh` means the module body is POSIX-shell syntax and
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
`shells=sh` means the module body is POSIX-shell syntax, and it is loaded by
|
|
80
|
+
**every POSIX shell** — `sh`, `bash`, `zsh`, `ksh`, and dash — because those
|
|
81
|
+
shells all run POSIX syntax. The engine classifies a running shell as `bash`,
|
|
82
|
+
`zsh`, or `ksh` when it can prove it, and as `sh` otherwise (dash, BusyBox
|
|
83
|
+
ash, and any other POSIX shell); `shells=sh` matches both the `sh` class and
|
|
84
|
+
the specific classes, so a `shells=sh` module loads in all of them. Write
|
|
85
|
+
portable bodies for `shells=sh` and you cover every POSIX shell at once.
|
|
86
|
+
|
|
87
|
+
`fish` is the exception: it selects the native Fish surface under
|
|
88
|
+
`lib/fish/`, and the Fish loader never sources POSIX shell bodies. A module
|
|
89
|
+
whose `shells=` list does not contain `fish` is skipped under Fish rather
|
|
90
|
+
than failed — a directly requested module fails closed in strict mode.
|
|
91
|
+
|
|
51
92
|
The engine ships a generic Fish adapter at `entrypoints/fish/profile.fish` as
|
|
52
93
|
the reference implementation of this handshake: it sets
|
|
53
94
|
`_sp_fish_loader_bootstrap_mode` and `_sp_fish_loader_bootstrap_seeds`, then sources `loader.fish`. The loader
|
|
@@ -125,7 +166,7 @@ The supported composition is a primary consumer root plus one
|
|
|
125
166
|
version-pinned layer root per provider.
|
|
126
167
|
The provider tree is vendored under `layer/<name>/` of the release
|
|
127
168
|
(preserving its internal `module.d/` + `modules/` layout), and a single
|
|
128
|
-
manifest covers the whole closure, so `verify
|
|
169
|
+
manifest covers the whole closure, so `release verify` checks consumer
|
|
129
170
|
and provider bytes together before anything is sourced.
|
|
130
171
|
|
|
131
172
|
- Pinning: record the provider source and revision beside the vendored
|
|
@@ -181,6 +222,7 @@ Supported `side_effect` values are:
|
|
|
181
222
|
- `update-network` — performs explicit update/check network behavior.
|
|
182
223
|
- `install-time` — belongs only in installer flows.
|
|
183
224
|
- `local-secret` — reads local secret references at runtime only.
|
|
225
|
+
- `local-state` — creates or caches state under the XDG state directory.
|
|
184
226
|
- `work-only` — work/project-specific behavior that must be opt-in.
|
|
185
227
|
|
|
186
228
|
`requires` dependencies must exist and load before the declaring module. Missing
|
|
@@ -211,11 +253,30 @@ and `tests/fish-runtime.test.sh` respectively:
|
|
|
211
253
|
quarantines them and continues. Unlike the POSIX fallback, quarantined
|
|
212
254
|
members are not loaded.
|
|
213
255
|
|
|
256
|
+
### Writing a cross-platform profile
|
|
257
|
+
|
|
258
|
+
`requires` means the module cannot work without the dependency, so a
|
|
259
|
+
dependency that does not support the host blocks the module and fails strict
|
|
260
|
+
mode. Use `optional` for platform-specific modules, so one profile works on
|
|
261
|
+
every host:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
name=profile_main
|
|
265
|
+
requires=common_tools
|
|
266
|
+
optional=mac_tools linux_tools
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Only the module for the running platform loads. The other is skipped without a
|
|
270
|
+
message, because its absence is expected. The POSIX loader and compiled plans
|
|
271
|
+
follow the same rules.
|
|
272
|
+
|
|
214
273
|
`exports` lists public commands or shell functions the module declares. The loader
|
|
215
274
|
uses this list for post-load export checks and failure cleanup; project catalogs
|
|
216
275
|
and wrapper manifests use it for documentation and registration. An exported name
|
|
217
|
-
must be defined by the
|
|
218
|
-
successfully loaded.
|
|
276
|
+
must be defined by the module body before the module is considered
|
|
277
|
+
successfully loaded. An export must name a function, so shell reserved words
|
|
278
|
+
(`case`, `do`, `done`, `elif`, `else`, `esac`, `fi`, `for`, `if`, `in`,
|
|
279
|
+
`then`, `until`, `while`, and `time`) are rejected.
|
|
219
280
|
|
|
220
281
|
`updates` lists update registrations declared by the module. Each value is an ABI
|
|
221
282
|
identifier that maps one-to-one to a row in the project update manifest. The
|
|
@@ -238,12 +299,19 @@ engine update mechanism.
|
|
|
238
299
|
|
|
239
300
|
The ordinary source phase rejects `generated-file`, `update-network`, and
|
|
240
301
|
`install-time` modules before sourcing their bodies. `read-only`, `path-env`,
|
|
241
|
-
`local-secret`, and `work-only` are permitted by the engine;
|
|
242
|
-
to load a `work-only` module is the consuming profile's
|
|
302
|
+
`local-secret`, `local-state`, and `work-only` are permitted by the engine;
|
|
303
|
+
choosing whether to load a `work-only` module is the consuming profile's
|
|
304
|
+
responsibility.
|
|
305
|
+
|
|
306
|
+
`local-state` is the permitted class for a module that creates a cache or
|
|
307
|
+
directory under the XDG state directory at source time (`mkdir -p` under
|
|
308
|
+
`$XDG_STATE_HOME`). It is the only write class the ordinary source phase
|
|
309
|
+
allows. A module that writes anywhere else must declare `generated-file`,
|
|
310
|
+
which the source phase rejects.
|
|
243
311
|
|
|
244
312
|
The enforcement hook is
|
|
245
313
|
`sp_metadata_validate_source_side_effect <side_effect>`. The loader calls it
|
|
246
|
-
before ordinary source invokes a load path
|
|
314
|
+
before ordinary source invokes a load path.
|
|
247
315
|
|
|
248
316
|
## Capability layer available to modules
|
|
249
317
|
|
|
@@ -255,6 +323,17 @@ lookup, OS detection, canonical path containment, symlink-component rejection,
|
|
|
255
323
|
Homebrew path discovery, secure local-bin permission checks, and background
|
|
256
324
|
process launch.
|
|
257
325
|
|
|
326
|
+
**The helpers stay available after the profile loads.** A compiled plan
|
|
327
|
+
sources the capability prelude and leaves it in place, so a function defined
|
|
328
|
+
by a module can call `sp_cap_os` or any other `sp_cap_*` helper long after
|
|
329
|
+
the plan finished loading. The compiled plan and the loader agree on this;
|
|
330
|
+
neither cleans the helpers up at the end of a load.
|
|
331
|
+
|
|
332
|
+
A module that needs a portable operation should call the `sp_cap_*` helper
|
|
333
|
+
rather than reaching for a platform-specific command directly. That keeps the
|
|
334
|
+
module working on every platform the engine supports, and keeps the
|
|
335
|
+
platform-specific code in one reviewed library.
|
|
336
|
+
|
|
258
337
|
Mutation paths must fail closed when a capability cannot prove safety. Update
|
|
259
338
|
destinations must combine canonical containment with symlink-component rejection
|
|
260
339
|
before writing, cooldown logic must use the mtime helper instead of raw
|
|
@@ -276,13 +355,11 @@ the loader after metadata validation, dependency sorting, shell/platform matchin
|
|
|
276
355
|
and phase side-effect checks have passed. The ABI has no unload hook: the loader
|
|
277
356
|
does not implement module rollback or unload semantics.
|
|
278
357
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
functions. The bridge hook is invoked only after the same identifier, dependency,
|
|
283
|
-
shell/platform, and phase checks that protect file-backed modules.
|
|
358
|
+
A meta module has no `load`; it has `requires` and no body. Nothing is
|
|
359
|
+
sourced for it, but its dependency closure loads and its dependencies'
|
|
360
|
+
export checks still apply.
|
|
284
361
|
|
|
285
|
-
Module bodies
|
|
362
|
+
Module bodies must treat `_spl_*`, `_sp_loader_*`, and
|
|
286
363
|
`_SP_LOADER_*` names as loader-private reserved namespaces. Native Fish
|
|
287
364
|
transactions additionally reserve `_sp_fish_loader_scope_before_*`,
|
|
288
365
|
`__sp_fish_loader_scope_before_*`, `_sp_fish_loader_scope_after_*`, and
|
|
@@ -295,8 +372,14 @@ module and must never use module-controlled private state for recursive cleanup.
|
|
|
295
372
|
Modules must also not define shell functions that shadow critical shell control
|
|
296
373
|
utilities (`command`, `unset`, `readonly`, `return`, or `eval`) or mark those
|
|
297
374
|
functions readonly. Those names are reserved because the loader must recover
|
|
298
|
-
control after sourcing a module; direct definitions in
|
|
299
|
-
|
|
375
|
+
control after sourcing a module; direct definitions in module bodies are
|
|
376
|
+
rejected before the body is invoked.
|
|
377
|
+
|
|
378
|
+
After a body runs, the loader restores the caller's physical directory, logical
|
|
379
|
+
`PWD` and `OLDPWD`, `IFS`, and the shell options it snapshotted. A body that
|
|
380
|
+
makes any of those variables readonly is rejected as caller-state modification:
|
|
381
|
+
the loader fails the module without aborting the caller. Cleanup resolves `rm`
|
|
382
|
+
from a fixed absolute path, so a module that changes `PATH` cannot prevent it.
|
|
300
383
|
|
|
301
384
|
Failure handling is selected once by the loader caller with
|
|
302
385
|
`VALISE_MODE=strict|permissive`; it is not a per-module metadata promise.
|
|
@@ -309,21 +392,6 @@ Permissive mode reports the affected module and continues with unrelated modules
|
|
|
309
392
|
paths, `..` path segments, spaces, quotes, shell metacharacters, and glob
|
|
310
393
|
characters are invalid. Reading metadata must not source these paths.
|
|
311
394
|
|
|
312
|
-
## Compatibility bridge
|
|
313
|
-
|
|
314
|
-
The legacy flat-gist modules expose dependencies by executing `_shell_module_*`
|
|
315
|
-
functions with `_SHMOD_GET_DEPS=1`. That behavior is compatibility evidence, not
|
|
316
|
-
the target discovery model. The bridge serializes each legacy module into the
|
|
317
|
-
same metadata shape with:
|
|
318
|
-
|
|
319
|
-
- `name=<legacy-name>`
|
|
320
|
-
- `requires=<legacy _SHMOD_DEPS>`
|
|
321
|
-
- `load_hook=_shell_module_<legacy-name>`
|
|
322
|
-
|
|
323
|
-
The bridge exists to preserve current dependency edges and permissive warning
|
|
324
|
-
behavior while the loader migrates to metadata. It must not be used to justify
|
|
325
|
-
executing arbitrary new module bodies during discovery.
|
|
326
|
-
|
|
327
395
|
## Validation implementation
|
|
328
396
|
|
|
329
397
|
`lib/metadata.sh` provides the current metadata parser and validator:
|
|
@@ -332,9 +400,5 @@ executing arbitrary new module bodies during discovery.
|
|
|
332
400
|
- `sp_metadata_validate_identifier <identifier>` validates ABI identifiers.
|
|
333
401
|
- `sp_metadata_validate_source_side_effect <side_effect>` rejects phase-unsafe
|
|
334
402
|
classes before ordinary startup can source a module body.
|
|
335
|
-
- `sp_metadata_legacy_bridge <name> <requires> <side-effect> <exports>` emits a
|
|
336
|
-
normalized compatibility record for an existing `_shell_module_*` module. Bridge
|
|
337
|
-
records include empty `updates=` unless a later migration task explicitly maps
|
|
338
|
-
legacy update registration.
|
|
339
403
|
|
|
340
404
|
The executable contract is `tests/loader/metadata-abi.test.sh`.
|
package/docs/release-policy.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Current status
|
|
4
4
|
|
|
5
|
-
`valise-sh` is at **0.
|
|
5
|
+
`valise-sh` is at **0.2.0**. Before 1.0, the CLI, module ABI, and release
|
|
6
6
|
format do not have a backward-compatibility guarantee. Consumers should pin
|
|
7
7
|
updates, review release notes, and run their profile's tests before upgrading.
|
|
8
8
|
|
|
@@ -19,26 +19,121 @@ The compatibility contract has three separate parts:
|
|
|
19
19
|
|
|
20
20
|
A change affecting one of these is a compatibility change even if the other
|
|
21
21
|
surfaces remain unchanged. Before 1.0, incompatible changes may occur between
|
|
22
|
-
minor releases
|
|
23
|
-
|
|
22
|
+
minor releases, and release notes must state what consumers have to change.
|
|
23
|
+
After 1.0, use semantic versioning: patch for compatible fixes,
|
|
24
24
|
minor for backward-compatible features, and major for incompatible changes.
|
|
25
25
|
|
|
26
|
+
## What changed in 0.2.0
|
|
27
|
+
|
|
28
|
+
`0.1.0` had no external users, so `0.2.0` provides no migration path for its
|
|
29
|
+
CLI or shell integration. The items below describe the 0.2.0 contract; each is
|
|
30
|
+
also recorded in [CHANGELOG.md](../CHANGELOG.md).
|
|
31
|
+
|
|
32
|
+
### The registry is a generated artifact
|
|
33
|
+
|
|
34
|
+
`shell/release/module-registry.tsv` is generated from the project's metadata
|
|
35
|
+
by `valise registry`, and `valise build` regenerates it while `valise registry
|
|
36
|
+
--check` validates it. A hand-written registry is no longer the source of truth, and a
|
|
37
|
+
project needs no registry file at all to build: it keeps its `.meta` files,
|
|
38
|
+
their bodies, the bundle roots, and the variants. A committed registry is
|
|
39
|
+
accepted as input, but it is overwritten on the next build.
|
|
40
|
+
|
|
41
|
+
The row order is fixed and does not depend on the caller's locale: the
|
|
42
|
+
primary modules of `shell/module.d/` first, then the layer modules of each
|
|
43
|
+
`shell/layers/<provider>/module.d/`, each group in C-locale name order.
|
|
44
|
+
|
|
45
|
+
### The metadata ABI gained fields and classes
|
|
46
|
+
|
|
47
|
+
- **Meta modules.** Metadata with `requires=` and no `load` is a
|
|
48
|
+
dependency-only aggregator. It loads successfully without a body. `load`
|
|
49
|
+
and `requires` may be combined.
|
|
50
|
+
- **`side_effect=local-state`.** A source-safe class for modules that write
|
|
51
|
+
under the XDG state directory. It loads in the ordinary phase where
|
|
52
|
+
`generated-file` still fails.
|
|
53
|
+
- **`shells=sh` covers every POSIX shell.** A module that declares `shells=sh`
|
|
54
|
+
loads under `sh`, `dash`, `bash`, `zsh`, and `ksh`, and is skipped under
|
|
55
|
+
Fish unless it is requested directly in strict mode.
|
|
56
|
+
|
|
57
|
+
### Compiled plans follow the loader
|
|
58
|
+
|
|
59
|
+
A compiled plan uses the loader's dependency rules. The required closure is
|
|
60
|
+
the bundle's roots plus their transitive `requires`. A module whose required
|
|
61
|
+
dependency is unavailable, failed, or skipped for the platform does not run.
|
|
62
|
+
Strict mode stops at the first failure in that closure and returns nonzero;
|
|
63
|
+
permissive mode reports each failure, continues with unrelated modules, and
|
|
64
|
+
returns zero. An optional dependency loads first when it is available for the
|
|
65
|
+
shell and variant; its absence never blocks the module that declared it. A
|
|
66
|
+
cross-platform profile declares platform-specific modules with `optional=`.
|
|
67
|
+
A meta module that declares exports is a compile error, and a module that
|
|
68
|
+
fails has its declared exports removed from the function namespace.
|
|
69
|
+
|
|
70
|
+
### Capabilities are available at runtime
|
|
71
|
+
|
|
72
|
+
The `sp_cap_*` helpers remain sourced and callable after a compiled plan or a
|
|
73
|
+
loader load finishes. A module may call `sp_cap_os` and the other helpers
|
|
74
|
+
from its own functions. A plan that removed them at the end of the load left
|
|
75
|
+
module functions unusable.
|
|
76
|
+
|
|
77
|
+
### Startup integration is managed
|
|
78
|
+
|
|
79
|
+
`valise install` writes a managed block into `~/.profile` and into each
|
|
80
|
+
further startup file it manages: the existing `~/.bash_profile` or
|
|
81
|
+
`~/.bash_login`, `~/.bashrc` and `~/.zshrc` when that shell is current or the
|
|
82
|
+
file exists, and `~/.zprofile` when zsh is on `PATH` or the file exists. It
|
|
83
|
+
does not create `~/.bash_profile` or `~/.bash_login`, because either would
|
|
84
|
+
stop bash from reading `~/.profile`. A Fish `conf.d` hook is written when
|
|
85
|
+
valise finds a Fish executable in a trusted location. `--no-integration`
|
|
86
|
+
writes no startup file and no Fish hook. `valise apply` uses the same writer
|
|
87
|
+
after building and installing.
|
|
88
|
+
|
|
89
|
+
Each POSIX file receives the block between `# BEGIN valise managed block` and
|
|
90
|
+
`# END valise managed block`. A repeat install replaces the block in place
|
|
91
|
+
and preserves the rest of the file. An unpaired marker is refused and stops
|
|
92
|
+
the install; a file already processed keeps its updated block. A symlinked
|
|
93
|
+
startup file is reported and left untouched.
|
|
94
|
+
|
|
95
|
+
### Commands
|
|
96
|
+
|
|
97
|
+
The command surface has one command per job. `valise init` takes `posix` or
|
|
98
|
+
`fish`; with no argument it prints the Fish hook when `$SHELL` names fish and
|
|
99
|
+
the POSIX block otherwise. `valise new`, `valise module`, `valise build`,
|
|
100
|
+
`valise apply`, `valise status`, and `valise registry` are new. `valise build`
|
|
101
|
+
builds a release tree, and `valise apply` builds, installs, writes the shell
|
|
102
|
+
integration, and verifies the result.
|
|
103
|
+
|
|
104
|
+
Installed releases are managed by `valise release list|use|prune|verify`.
|
|
105
|
+
`variant list`, `variant use`, and `verify-active` are gone; `variant` now
|
|
106
|
+
refers only to a build variant. `valise doctor` gained `--pre-install` and
|
|
107
|
+
`--no-integration`; it reads the startup files the running shell actually
|
|
108
|
+
reads and exits nonzero when the active release is missing or fails
|
|
109
|
+
verification, or when no startup file carries the block. `valise
|
|
110
|
+
explain-bundles` and `valise layer-lint` no longer use Python.
|
|
111
|
+
|
|
26
112
|
## Release checklist
|
|
27
113
|
|
|
28
114
|
Before tagging or publishing a release:
|
|
29
115
|
|
|
30
116
|
1. Update `package.json` and `bin/valise` version together; add a dated entry
|
|
31
|
-
to [CHANGELOG.md](../CHANGELOG.md).
|
|
117
|
+
to [CHANGELOG.md](../CHANGELOG.md). The CLI's test suite asserts that the
|
|
118
|
+
two versions match, so a bump that updates one and forgets the other
|
|
119
|
+
fails the build.
|
|
32
120
|
2. Run `make check-fast`, then run `make VALISE_REQUIRE_TOOLING=1 check`
|
|
33
121
|
locally or use the GitHub Actions job. Keep the successful strict run as
|
|
34
122
|
release evidence for every lane, including Fish, zsh, BusyBox, dash, and
|
|
35
123
|
ksh.
|
|
36
124
|
3. Run the README isolated quickstart from a clean checkout and verify that
|
|
37
|
-
install, `verify
|
|
125
|
+
install, `release verify`, and example module loading work in the temporary
|
|
38
126
|
home. Confirm cleanup removes the entire demo state.
|
|
39
127
|
4. Inspect the result of `npm pack --dry-run` and install/test the packed
|
|
40
128
|
tarball. The CLI's tests currently cover pack contents, installation, and
|
|
41
|
-
module loading.
|
|
129
|
+
module loading. The probe in `tests/cli-posix.test.sh` builds a tree from
|
|
130
|
+
the real `files` whitelist, plants build directories at more than one
|
|
131
|
+
depth under `examples/`, and requires the tarball to exclude every one of
|
|
132
|
+
them while keeping the surrounding example sources. `package.json` keeps
|
|
133
|
+
them out with `!examples/**/build/`, because npm applies `files` without
|
|
134
|
+
consulting `.gitignore` and `valise apply` writes build output into
|
|
135
|
+
`examples/profile-project/`. The `**` is load-bearing: a single `*` still
|
|
136
|
+
packs a build directory nested one level deeper.
|
|
42
137
|
5. Review the changed CLI, module ABI, release manifest, docs, and examples;
|
|
43
138
|
check `git diff --check` and doc links.
|
|
44
139
|
6. Publish only through the chosen distribution channel. The repository
|
|
@@ -48,3 +143,12 @@ Before tagging or publishing a release:
|
|
|
48
143
|
|
|
49
144
|
Do not claim a platform or shell has passed unless its runtime lane actually
|
|
50
145
|
ran. An explicit skip is useful development evidence, not release evidence.
|
|
146
|
+
|
|
147
|
+
## Test isolation
|
|
148
|
+
|
|
149
|
+
Every suite runs in a quarantined environment: a fresh temporary `HOME`,
|
|
150
|
+
controlled `TMPDIR` and `PATH`, and cleared startup hooks. A suite must also
|
|
151
|
+
pass when it is run directly rather than through `make`, because the Makefile
|
|
152
|
+
pins `TMPDIR=/tmp` and that can hide a defect that a developer's own
|
|
153
|
+
environment exposes. macOS sets `TMPDIR` to a path ending in a slash, so a
|
|
154
|
+
path comparison in a suite must resolve both sides before comparing them.
|
package/docs/shared-modules.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Shared modules: governance and index
|
|
2
2
|
|
|
3
3
|
This policy covers the shared modules shipped here and the requirements for
|
|
4
|
-
provider repositories. It defines the layer namespace,
|
|
5
|
-
and how modules are indexed, deprecated, and
|
|
4
|
+
provider repositories. It defines the layer namespace, what belongs in a
|
|
5
|
+
layer, review expectations, and how modules are indexed, deprecated, and
|
|
6
|
+
removed.
|
|
6
7
|
|
|
7
8
|
## Namespace and ownership
|
|
8
9
|
|
|
@@ -24,6 +25,121 @@ review responsibility.
|
|
|
24
25
|
discover what exists; they read this index to discover what the engine
|
|
25
26
|
ships.
|
|
26
27
|
|
|
28
|
+
### Use an owner segment
|
|
29
|
+
|
|
30
|
+
The `contrib_` prefix is the only part the tools enforce. Nothing
|
|
31
|
+
mechanically distinguishes one provider from another, so two providers that
|
|
32
|
+
both ship `contrib_path_utils` fail closed against each other with a
|
|
33
|
+
same-name conflict, and neither can be swapped for the other.
|
|
34
|
+
|
|
35
|
+
**Use a provider segment after the prefix: `contrib_<owner>_<thing>`.**
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
contrib_acme_widget_setup
|
|
39
|
+
contrib_acme_widget_apply
|
|
40
|
+
contrib_globex_path_helpers
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This is a recommendation, not a rule the tools check, but it is what keeps
|
|
44
|
+
two providers from colliding. A layer should use one owner segment
|
|
45
|
+
consistently across all of its modules, so a consumer can see at a glance
|
|
46
|
+
which provider owns what.
|
|
47
|
+
|
|
48
|
+
If you must ship a module whose name another provider might already use,
|
|
49
|
+
prefix it with your owner segment anyway — a name collision between two
|
|
50
|
+
vendored providers is otherwise unresolvable without renaming one of them.
|
|
51
|
+
|
|
52
|
+
## What belongs in a layer
|
|
53
|
+
|
|
54
|
+
A layer is a vendorable set of modules: something another person can take
|
|
55
|
+
unchanged, use, and maintain on their own terms. The practical test is
|
|
56
|
+
simple: **could another person vendor this module unchanged and still be
|
|
57
|
+
happy with it?**
|
|
58
|
+
|
|
59
|
+
A layer module must have:
|
|
60
|
+
|
|
61
|
+
- **No employer material.** No account IDs, hostnames, cloud profiles,
|
|
62
|
+
project names, or anything else tied to one organization.
|
|
63
|
+
- **No personal configuration.** No hard-coded home paths, no chosen
|
|
64
|
+
editor, no particular toolchain, model, or theme.
|
|
65
|
+
- **No dependency outside the layer**, apart from the engine's own modules.
|
|
66
|
+
A layer module may require an engine module, but it may not require a
|
|
67
|
+
module that lives in the consumer's primary root — that dependency cannot
|
|
68
|
+
be satisfied when someone else vendors the layer.
|
|
69
|
+
|
|
70
|
+
Everything else stays in the primary root: environment setup, completions,
|
|
71
|
+
tool wrappers, work-specific modules, and the aggregators.
|
|
72
|
+
|
|
73
|
+
`valise layer-lint <layer-name>` enforces the mechanical half of this. It
|
|
74
|
+
checks that the layer has a `module.d/` directory, that every metadata file
|
|
75
|
+
is a regular file with a safe mode, that every module name carries the
|
|
76
|
+
`contrib_` prefix, that the metadata validates as data, that the version is
|
|
77
|
+
present, that the load target exists as a regular file, and that every
|
|
78
|
+
dependency resolves inside the layer or to an engine module. It needs no
|
|
79
|
+
Python.
|
|
80
|
+
|
|
81
|
+
### 1. Separate generic engines from opinionated presets
|
|
82
|
+
|
|
83
|
+
A reusable mechanism and the opinionated defaults built on it are different
|
|
84
|
+
modules with different audiences, and they belong in different files.
|
|
85
|
+
|
|
86
|
+
The engine is the part anyone might want. The preset is a list of specific
|
|
87
|
+
choices that only some users want. Ship them as two modules so a consumer
|
|
88
|
+
can take the engine without the presets.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
contrib_gitignore_engine # the matching logic
|
|
92
|
+
contrib_gitignore_preset # one opinionated list of paths
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A consumer who wants their own path list takes the engine and writes their
|
|
96
|
+
own preset. A consumer who wants the shipped defaults takes both. If the
|
|
97
|
+
engine and the preset were one module, every consumer would be forced to
|
|
98
|
+
either accept the opinionated list or fork the whole thing.
|
|
99
|
+
|
|
100
|
+
### 2. Avoid grab-bag modules
|
|
101
|
+
|
|
102
|
+
A module's dependencies and `shells=` are the union of its parts. Splitting
|
|
103
|
+
one module into several narrow ones lets each part declare only what it
|
|
104
|
+
actually needs.
|
|
105
|
+
|
|
106
|
+
The failure this avoids is concrete: a helper that used `local` was bundled
|
|
107
|
+
into a general-purpose utilities module, and because `local` is not POSIX,
|
|
108
|
+
that one function excluded ksh for **every** function in the module. The
|
|
109
|
+
module's `shells=` list had to be the union, so one non-portable helper
|
|
110
|
+
cost the whole module its ksh support.
|
|
111
|
+
|
|
112
|
+
Split instead:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
contrib_owner_cli_quote # POSIX, no dependencies
|
|
116
|
+
contrib_owner_cli_local # needs a non-POSIX shell
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Now the first module keeps `shells=sh bash zsh ksh` and the second declares
|
|
120
|
+
what it actually needs. The same rule applies to dependencies: a module that
|
|
121
|
+
pulls in a heavy dependency should be separate from one that does not.
|
|
122
|
+
|
|
123
|
+
### 3. Use owner namespaces
|
|
124
|
+
|
|
125
|
+
Covered above: `contrib_<owner>_<thing>`, one segment per provider, used
|
|
126
|
+
consistently.
|
|
127
|
+
|
|
128
|
+
### 4. Runtime capability use is allowed
|
|
129
|
+
|
|
130
|
+
A layer module **may** call `sp_cap_*` helpers at run time. A compiled plan
|
|
131
|
+
sources the capability prelude and leaves it in place, so a function the
|
|
132
|
+
module defines can call `sp_cap_os`, `sp_cap_cpu_count`, or any other helper
|
|
133
|
+
long after the profile finished loading. The loader behaves the same way.
|
|
134
|
+
|
|
135
|
+
This is a change from earlier engine behavior, where the helpers were
|
|
136
|
+
removed after loading and a module function that called one broke. If you
|
|
137
|
+
have a layer that avoided the helpers to work around that, you can now use
|
|
138
|
+
them directly.
|
|
139
|
+
|
|
140
|
+
Prefer the `sp_cap_*` helpers over open-coding platform-specific commands.
|
|
141
|
+
See [contrib-modules.md](contrib-modules.md) for the list.
|
|
142
|
+
|
|
27
143
|
## Index of engine-shipped shared modules
|
|
28
144
|
|
|
29
145
|
| Module | Version | Exports | Source | Status |
|
|
@@ -42,6 +158,10 @@ The example provider tree in this repository
|
|
|
42
158
|
| `contrib_shared_paths` | 1.0.0 | `contrib_shared_paths_setup` | `examples/shared/modules/shared/contrib_shared_paths.sh` | active |
|
|
43
159
|
| `contrib_shared_umask` | 1.0.0 | `contrib_shared_umask_secure` | `examples/shared/modules/shared/contrib_shared_umask.sh` | active |
|
|
44
160
|
|
|
161
|
+
These engine-shipped modules predate the owner-segment recommendation and
|
|
162
|
+
keep their existing names; renaming them would break every consumer that
|
|
163
|
+
already vendors them. New modules should use the owner-segment form.
|
|
164
|
+
|
|
45
165
|
Update the index in the same change that adds, renames, deprecates, or
|
|
46
166
|
removes a shared module. Provider repositories should maintain a corresponding
|
|
47
167
|
index for their own layer modules.
|
|
@@ -53,6 +173,8 @@ Every change to a shared module passes the same floor:
|
|
|
53
173
|
1. The engine gate: `make check-fast` during iteration and
|
|
54
174
|
`make VALISE_REQUIRE_TOOLING=1 check` before a release claim (strict
|
|
55
175
|
tooling is automatic when CI sets `CI=true` or `GITHUB_ACTIONS=true`).
|
|
176
|
+
These targets live in the source repository; the npm package ships the
|
|
177
|
+
runtime, the docs, and the examples, not the Makefile.
|
|
56
178
|
2. The authoring checklist in
|
|
57
179
|
[contrib-modules.md](contrib-modules.md): metadata validates, the
|
|
58
180
|
module loads strict from a clean environment, behavior is idempotent
|