[chore] commit id display on version
Publish snapshot / snapshot (push) Successful in 1m26s

This commit is contained in:
Benjamin Diedrichsen
2026-07-29 13:00:10 +02:00
parent ea08e76a2f
commit 1ba1c2a32a
17 changed files with 589 additions and 56 deletions
+7 -6
View File
@@ -325,8 +325,9 @@ mirror the path, and they are claimed **globally** — across `cubeDirs`,
- a duplicate id (the message names every claimant and how each got into the run);
- a manifest that throws on import, exports a non-object, or has no `name`;
- a `secrets` entry naming a key that is not in the schema;
- a package in `cubePackages` that is not installed, cannot be read, or declares
no `nopy.cubes`;
- a package in `cubePackages` that is not installed, cannot be read, has neither a
`cubes/` directory nor a `nopy.cubes` override, or whose `nopy.cubes` is not a
non-empty array of strings;
- a `nopy.cubes` entry that does not exist or points outside its package root.
Any of them aborts the run (`nopy.main.ts` returns before the workflow). None is
@@ -380,7 +381,7 @@ const { packages, errors } = resolveCubePackages(config.cubePackages);
interface CubePackage {
name: string; // the name it was requested under
root: string; // absolute path to the package root
dirs: string[]; // absolute paths, from the package's `nopy.cubes` field
dirs: string[]; // absolute paths: `<root>/cubes`, or the package's `nopy.cubes`
}
```
@@ -892,8 +893,8 @@ Nothing feeds the result into the built command — see
const { selectedCubes } = await CubeSelection(cubes); // string[] of ids
```
Multi-select with fuzzy filtering on the rendered label. A cancelled prompt
returns an empty array rather than throwing.
Multi-select with fuzzy filtering on the rendered label. A prompt dismissed with
Escape returns an empty array rather than throwing.
### `HostSelection(hosts)`
@@ -1182,7 +1183,7 @@ Real behaviour that a reader would otherwise take on trust. Tracked in
`Cube.getDefaults()`, against `{}` — and prompt input is type-coerced, which is
not the same thing.
- **Nothing checks bundle/CLI compatibility.** A cube package declares no
supported nopy range and the loader reads whatever `nopy.cubes` points at.
supported nopy range and the loader scans whatever directories it finds.
- **`self-update` reports an empty channel as unreachable.** `latest === null`
means either the request failed *or* the registry answered normally and the
dist-tag simply has no version — the second is exactly what a Gitea package
+26 -12
View File
@@ -22,13 +22,13 @@ The rest of this document is for writing one.
## What a bundle is
An ordinary npm package that ships cube directories and points at them from its
own `package.json`. There is no build step, no plugin API and no entry point —
nopy reads the directories off disk and imports each `manifest.mjs` directly.
An ordinary npm package that ships its cubes in a `cubes/` directory. There is no
build step, no plugin API and no entry point — nopy reads the directory off disk
and imports each `manifest.mjs` directly.
```
@acme/cubes-web
├── package.json nopy.cubes → ["./cubes"]
├── package.json no nopy block needed
├── README.md
└── cubes/
├── nginx/
@@ -50,7 +50,6 @@ special-cased.
"name": "@acme/cubes-web",
"version": "1.0.0",
"type": "module",
"nopy": { "cubes": ["./cubes"] },
"files": ["cubes", "!cubes/**/*.log", "README.md", "LICENSE"],
"publishConfig": { "access": "public" },
"dependencies": {
@@ -60,10 +59,24 @@ special-cased.
}
```
**`nopy.cubes`** is the only field nopy requires. It is an array of directories,
relative to the package root, each scanned recursively for cubes. Several
entries are fine; a single `["./cubes"]` is the norm. Every entry must exist and
must stay inside the package — a path escaping the root is refused, not resolved.
**Nothing declares the cubes.** `cubes/` at the package root is the convention,
scanned recursively, and a bundle that follows it needs no nopy-specific field at
all. Naming the package in `cubePackages` is already the statement that cubes are
expected from it.
**`nopy.cubes`** overrides that, for the bundle whose cubes are somewhere else — a
package compiled from TypeScript sources into `dist/cubes`, say, or one shipping
two separate trees:
```json
"nopy": { "cubes": ["./dist/cubes", "./contrib"] }
```
It is an array of directories relative to the package root. Every entry must
exist and must stay inside the package — a path escaping the root is refused, not
resolved. Present-but-malformed (an empty array, a bare string, non-strings) is an
error rather than a fall back to the default: saying something that does not parse
is not the same as saying nothing.
**`type: "module"`** matters: manifests are ESM. Without it a `manifest.mjs` still
loads (the extension carries the day), but anything it imports relatively will
@@ -222,7 +235,7 @@ Nothing bundle-specific: `npm publish` (or `pnpm publish`) with a version bump.
Some things worth deciding once:
- **Version the bundle independently of nopy.** There is no compatibility check
between the two — the loader reads whatever `nopy.cubes` points at. Document
between the two — the loader scans whatever directories it finds. Document
the nopy version you test against in your README.
- **Renaming or removing an id is breaking.** It invalidates recorded sessions
and breaks any manifest listing it as a dependency, including manifests in
@@ -254,8 +267,9 @@ For how this repository releases its own packages, see
| Symptom | Cause |
| --- | --- |
| `Cube package 'X' is not installed (looked up from …)` | Not installed, or installed somewhere other than the config that named it. The path in the message is where the lookup started. |
| `Cube package 'X' declares no cubes` | Missing or malformed `nopy.cubes` in the package's `package.json`. It must be a non-empty array of strings. |
| `'./cubes' does not exist in` | The directory was not packed. Check `files` and `npm pack --dry-run`. |
| `Cube package 'X' has no cubes/ directory in …` | No `cubes/` at the package root and no `nopy.cubes` pointing elsewhere. Usually the directory was not packed — check `files` and `npm pack --dry-run`. |
| `"nopy": { "cubes": … } must be a non-empty array of strings` | The override is present but malformed. Fix it, or omit it entirely to use `./cubes`. |
| `'…' does not exist in …` | A `nopy.cubes` entry pointing at a directory the tarball does not contain. |
| `'…' points outside the package` | A `nopy.cubes` entry escaping the package root. Not allowed. |
| `Duplicate cube id 'X' from N sources:` | Two or more cubes claiming one id; the message lists each source. Rename one — there is no precedence rule to lean on. |
| `ERR_MODULE_NOT_FOUND` for `zod` or `@bitsquare/nopy-cube` | The bundle did not declare them as dependencies. The resolve-hook fallback covers loose local cubes, not published packages. |
+14 -7
View File
@@ -141,7 +141,6 @@ A cube bundle is an npm package with a `nopy` field:
"name": "@acme/cubes-net",
"version": "1.0.0",
"type": "module",
"nopy": { "cubes": ["./cubes"] },
"files": ["cubes", "README.md", "LICENSE"],
"keywords": ["nopy", "nopy-cubes", "pyinfra"],
"dependencies": {
@@ -154,10 +153,16 @@ A cube bundle is an npm package with a `nopy` field:
Rules:
- `nopy.cubes` — directories relative to the package root, scanned exactly like
`cubeDirs` entries. Required; a package listed in `cubePackages` without a
`nopy` field is an error, not a silent skip. Listing it means the user expects
cubes from it.
- **Cube location is a convention, `<root>/cubes`.** *(Amended after Phase 5;
originally `nopy.cubes` was a required field.)* The field bought nothing a
convention does not: it is not a discovery marker — naming the package in
`cubePackages` already is one — and it says nothing about whether the
directories were actually packed, which is the failure authors really hit.
`nopy.cubes` remains as an **override**, directories relative to the package
root, for the bundle whose cubes are elsewhere (`dist/cubes` after a build).
Absent means the default; present-and-malformed is an error rather than a fall
back. Finding no cube directory at all is still an error, not a silent skip:
listing a package means the user expects cubes from it.
- Both dependencies are **regular dependencies, not peers**, and both are
load-bearing: a manifest imports `Manifest` from `@bitsquare/nopy-cube` and `z`
from `zod`. `@bitsquare/nopy-cube` peer-depends on zod, so the bundle's copy is
@@ -255,7 +260,8 @@ Errors (each aborts the run, consistent with the existing `errors` contract):
- package not found on any candidate path
- `package.json` unparseable
- no `nopy.cubes`, or it is not a non-empty array of strings
- neither a `cubes/` directory nor a `nopy.cubes` override
- `nopy.cubes` present but not a non-empty array of strings
- a `nopy.cubes` entry escapes the package root, or does not exist
### Wiring
@@ -625,7 +631,8 @@ runner alike.
- resolves through a symlinked package directory (mimicking pnpm)
- resolves from the declaring config's directory, not `cwd`
- missing package → error naming the spec
- package without `nopy.cubes`error
- package without `nopy.cubes`falls back to `cubes/`
- package with neither → error; malformed `nopy.cubes` → error, no fall back
- `nopy.cubes` entry that does not exist, and one that escapes the root → errors
- last-wins dedupe when parent and child config both name a package