2 Commits

Author SHA1 Message Date
Benjamin Diedrichsen 4c0fe528dc fixing documentation
Publish snapshot / snapshot (push) Successful in 1m2s
2026-07-29 13:21:04 +02:00
Benjamin Diedrichsen 7e703c93b1 streamline package naming 2026-07-29 13:07:34 +02:00
113 changed files with 213 additions and 211 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ jobs:
# Pass 1: stamp every manifest before anything is packed. `pnpm # Pass 1: stamp every manifest before anything is packed. `pnpm
# publish` substitutes `workspace:*` with the version the linked # publish` substitutes `workspace:*` with the version the linked
# package declares at pack time, so nopy-cube has to be carrying its # package declares at pack time, so nopy-cubes has to be carrying its
# snapshot version by the time nopy is packed. # snapshot version by the time nopy is packed.
for dir in $dirs; do for dir in $dirs; do
base=$(node -p "require('./${dir}/package.json').version") base=$(node -p "require('./${dir}/package.json').version")
+3 -3
View File
@@ -1,13 +1,13 @@
# Tag-driven release of a single package. # Tag-driven release of a single package.
# #
# git tag nopy-v1.2.0 && git push origin nopy-v1.2.0 # git tag nopy-v1.2.0 && git push origin nopy-v1.2.0
# git tag nopy-cube-v1.2.0 && git push origin nopy-cube-v1.2.0 # git tag nopy-cubes-v1.2.0 && git push origin nopy-cubes-v1.2.0
# git tag keyman-v1.2.0 && git push origin keyman-v1.2.0 # git tag keyman-v1.2.0 && git push origin keyman-v1.2.0
# #
# The tag is the source of truth for *which* package ships; package.json is the # The tag is the source of truth for *which* package ships; package.json is the
# source of truth for the version, and the two must agree or the run fails. # source of truth for the version, and the two must agree or the run fails.
# #
# Packages that link to each other release dependency-first — `nopy-cube` before # Packages that link to each other release dependency-first — `nopy-cubes` before
# `nopy` — because the linked version is resolved at pack time. The run refuses # `nopy` — because the linked version is resolved at pack time. The run refuses
# to publish otherwise. # to publish otherwise.
# A version with a prerelease part (1.2.0-rc.1) publishes under `next` instead # A version with a prerelease part (1.2.0-rc.1) publishes under `next` instead
@@ -135,7 +135,7 @@ jobs:
# package declares at this commit. If that version is not on the # package declares at this commit. If that version is not on the
# registry yet, the release installs to a broken tree — and npmjs # registry yet, the release installs to a broken tree — and npmjs
# only lets you unpublish for 72 hours. Release the dependency first: # only lets you unpublish for 72 hours. Release the dependency first:
# nopy-cube, then nopy, then any bundle. # nopy-cubes, then nopy, then any bundle.
# #
# npmjs only: it is the irreversible one, and it needs no credentials # npmjs only: it is the irreversible one, and it needs no credentials
# to read, which this step does not have yet. # to read, which this step does not have yet.
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"hosts": [], "hosts": [],
"cubeDirs": [], "cubeDirs": [],
"cubePackages": ["@bitsquare/cubes-core"], "cubePackages": ["@bitsquare/nopy-cubes-core"],
"env": {}, "env": {},
"log": { "log": {
"verbosity": "info", "verbosity": "info",
+16 -16
View File
@@ -8,17 +8,17 @@ A pnpm workspace holding two independently published CLIs, the authoring package
their deployment units are written against, and one bundle of those units: their deployment units are written against, and one bundle of those units:
| Path | Package | Binary | Role | | Path | Package | Binary | Role |
| --------------------- | ---------------------- | -------- | -------------------------------------------------------- | | -------------------------- | ----------------------------- | -------- | -------------------------------------------------------- |
| `packages/nopy` | `@bitsquare/nopy` | `nopy` | interactive pyinfra script management and execution | | `packages/nopy` | `@bitsquare/nopy` | `nopy` | interactive pyinfra script management and execution |
| `packages/keyman` | `@bitsquare/keyman` | `keyman` | SSH key management, shelling out to `age` / `ssh-keygen` | | `packages/keyman` | `@bitsquare/keyman` | `keyman` | SSH key management, shelling out to `age` / `ssh-keygen` |
| `packages/nopy-cube` | `@bitsquare/nopy-cube` | — | the authoring surface a `manifest.mjs` imports | | `packages/nopy-cubes` | `@bitsquare/nopy-cubes` | — | the authoring surface a `manifest.mjs` imports |
| `packages/cubes-core` | `@bitsquare/cubes-core`| — | the core cube bundle (22 cubes), no TypeScript | | `packages/nopy-cubes-core` | `@bitsquare/nopy-cubes-core` | — | the core cube bundle (22 cubes), no TypeScript |
The root package is private; everything under `packages/` ships. `keyman` stands The root package is private; everything under `packages/` ships. `keyman` stands
alone, but `nopy` and `cubes-core` both depend on `nopy-cube` (`workspace:*`), so alone, but `nopy` and `nopy-cubes-core` both depend on `nopy-cubes` (`workspace:*`), so
publish order matters — see *Releasing*. publish order matters — see *Releasing*.
`cubes-core` is consumed the way a third party would consume it: the root `nopy-cubes-core` is consumed the way a third party would consume it: the root
`.nopyrc.json` names it in `cubePackages`, and the loader reads it out of `.nopyrc.json` names it in `cubePackages`, and the loader reads it out of
`node_modules`. There is no `cubes/` directory at the repo root any more. `node_modules`. There is no `cubes/` directory at the repo root any more.
@@ -76,11 +76,11 @@ locally and on the runner. Barrel files (`src/index.ts`, `src/cubes/index.ts`,
`src/nopy.cubes.ts`) and the Commander argv wiring (`src/*.cli.ts`) are excluded; `src/nopy.cubes.ts`) and the Commander argv wiring (`src/*.cli.ts`) are excluded;
adding logic to those files means moving it somewhere covered. adding logic to those files means moving it somewhere covered.
nopy's vitest config aliases `@bitsquare/nopy-cube` to that package's **source**, nopy's vitest config aliases `@bitsquare/nopy-cubes` to that package's **source**,
not to the workspace link (which points at a `dist` that only exists after a not to the workspace link (which points at a `dist` that only exists after a
build), so the gate does not depend on build ordering and can never run against a build), so the gate does not depend on build ordering and can never run against a
stale artefact. The same config excludes `**/nopy-cube/**` from coverage — without stale artefact. The same config excludes `**/nopy-cubes/**` from coverage — without
it nopy's numbers absorb another package's files. `cubes-core` has no tests of its it nopy's numbers absorb another package's files. `nopy-cubes-core` has no tests of its
own; the loader tests in nopy cover the contract it implements. own; the loader tests in nopy cover the contract it implements.
The three TS packages set `pool: 'forks'` because tests use `process.chdir()` — most The three TS packages set `pool: 'forks'` because tests use `process.chdir()` — most
@@ -184,15 +184,15 @@ items 6 and 7.
A cube directory holds `manifest.mjs` + `deploy.py`; anything else in it is A cube directory holds `manifest.mjs` + `deploy.py`; anything else in it is
ignored by the loader but reachable from the script, which runs with the cube ignored by the loader but reachable from the script, which runs with the cube
directory as its cwd. Manifests are ESM, import `Manifest` from directory as its cwd. Manifests are ESM, import `Manifest` from
`@bitsquare/nopy-cube`, and declare `id`, `name`, a Zod `schema` (each field `@bitsquare/nopy-cubes`, and declare `id`, `name`, a Zod `schema` (each field
`.describe()`d — the description is the prompt label — and `.default()`ed), plus `.describe()`d — the description is the prompt label — and `.default()`ed), plus
optional `secrets`/`dependencies`/`before`/`after`. optional `secrets`/`dependencies`/`before`/`after`.
Import from **`@bitsquare/nopy-cube`**, not `@bitsquare/nopy`. The authoring Import from **`@bitsquare/nopy-cubes`**, not `@bitsquare/nopy`. The authoring
surface is types and a factory, with zod as its only peer — no CLI, no prompts, surface is types and a factory, with zod as its only peer — no CLI, no prompts,
no process spawning — so a bundle can depend on it without dragging the CLI in. no process spawning — so a bundle can depend on it without dragging the CLI in.
`@bitsquare/nopy` re-exports all of it (`cubes.Manifest`, `cubes.uniqid`, …), so `@bitsquare/nopy` re-exports all of it (`cubes.Manifest`, `cubes.uniqid`, …), so
the older form still works; every cube in `packages/cubes-core` has been moved to the older form still works; every cube in `packages/nopy-cubes-core` has been moved to
the new one. the new one.
Manifests are resolved by ordinary Node resolution **from the manifest's own Manifests are resolved by ordinary Node resolution **from the manifest's own
@@ -200,7 +200,7 @@ directory**, which used to mean a hand-written local cube failed with
`ERR_MODULE_NOT_FOUND` unless you linked the package. `cubes/resolve-hook.mjs` `ERR_MODULE_NOT_FOUND` unless you linked the package. `cubes/resolve-hook.mjs`
retires that: `loadCubes()` registers a `module.register()` resolve hook that retires that: `loadCubes()` registers a `module.register()` resolve hook that
tries normal resolution *first* and only on failure falls back to resolving tries normal resolution *first* and only on failure falls back to resolving
`@bitsquare/nopy-cube`, `@bitsquare/nopy` and `zod` from the running CLI's own `@bitsquare/nopy-cubes`, `@bitsquare/nopy` and `zod` from the running CLI's own
`node_modules`. Ordinary-resolution-first is the load-bearing part — a cube that `node_modules`. Ordinary-resolution-first is the load-bearing part — a cube that
ships its own zod keeps it. The hook is a convenience, never load-bearing: ships its own zod keeps it. The hook is a convenience, never load-bearing:
registration is wrapped in a `try`, and a bundle installed properly never reaches registration is wrapped in a `try`, and a bundle installed properly never reaches
@@ -242,7 +242,7 @@ The install command uses `--@bitsquare:registry=<url>`, never `--registry`:
Gitea serves the `@bitsquare` scope and does **not** proxy npmjs, so a global Gitea serves the `@bitsquare` scope and does **not** proxy npmjs, so a global
`--registry` would send every transitive dependency to a registry that has never `--registry` would send every transitive dependency to a registry that has never
heard of them. Verified — `npm i -g @bitsquare/nopy@main --@bitsquare:registry=…` heard of them. Verified — `npm i -g @bitsquare/nopy@main --@bitsquare:registry=…`
pulls `nopy-cube` from Gitea and the other 55 packages from npmjs. pnpm accepts pulls `nopy-cubes` from Gitea and the other 55 packages from npmjs. pnpm accepts
the same flag; the `npm_config_@bitsquare:registry` env var does not work with the same flag; the `npm_config_@bitsquare:registry` env var does not work with
pnpm and is not used. pnpm and is not used.
@@ -330,7 +330,7 @@ Three things the `workspace:*` links added, all of them non-obvious:
`workspace:` range survived into a tarball. It runs in both publish workflows. `workspace:` range survived into a tarball. It runs in both publish workflows.
Note `pnpm pack` has no `--ignore-scripts` flag, so `prepack` does rebuild — Note `pnpm pack` has no `--ignore-scripts` flag, so `prepack` does rebuild —
which means the artefact under test is the one publish ships. which means the artefact under test is the one publish ships.
- **Order.** `packages/*/` sorts `nopy` before `nopy-cube`, which is backwards. - **Order.** `packages/*/` sorts `nopy` before `nopy-cubes`, which is backwards.
`scripts/publish-order.mjs` topologically sorts over the `workspace:` edges; `scripts/publish-order.mjs` topologically sorts over the `workspace:` edges;
the snapshot workflow stamps *every* version first and only then publishes in the snapshot workflow stamps *every* version first and only then publishes in
that order, because `pnpm publish` reads the linked package's version at pack that order, because `pnpm publish` reads the linked package's version at pack
@@ -347,9 +347,9 @@ currently have no effect. Treat `docs/REFACTORING.md` as a plan, not a record.
The publish lane has now run against the Gitea registry: all four packages are The publish lane has now run against the Gitea registry: all four packages are
there under `@main`, and `pnpm run try:snapshot` installs them into a throwaway there under `@main`, and `pnpm run try:snapshot` installs them into a throwaway
project with npm and runs the binary. The npmjs lane has only ever published project with npm and runs the binary. The npmjs lane has only ever published
`@bitsquare/nopy`; `keyman`, `nopy-cube` and `cubes-core` have never been `@bitsquare/nopy`; `keyman`, `nopy-cubes` and `nopy-cubes-core` have never been
released there, so the *check linked deps are released* guard in `release.yml` released there, so the *check linked deps are released* guard in `release.yml`
will stop the first `nopy` release until `nopy-cube` ships. will stop the first `nopy` release until `nopy-cubes` ships.
Nothing checks that a bundle and the CLI reading it are compatible versions; Nothing checks that a bundle and the CLI reading it are compatible versions;
`nopy.engines` was considered and deferred. `docs/CUBE-PACKAGES.md` is where all `nopy.engines` was considered and deferred. `docs/CUBE-PACKAGES.md` is where all
+1 -1
View File
@@ -206,7 +206,7 @@ Two pairs are inverted, and both have consequences:
> to deploy a cube whose required key nothing supplied. Verified against all 22 > to deploy a cube whose required key nothing supplied. Verified against all 22
> cubes in `cubes/`: 19 build a complete `-D` run, the 3 below abort by name. > cubes in `cubes/`: 19 build a complete `-D` run, the 3 below abort by name.
> >
> Re-measured against the 25 cubes now in `packages/cubes-core/cubes`: 20 build a > Re-measured against the 25 cubes now in `packages/nopy-cubes-core/cubes`: 20 build a
> complete `-D` run and 5 abort by name. Two of the additions are deliberate — > complete `-D` run and 5 abort by name. Two of the additions are deliberate —
> `user:add` lost its `PUBKEY` default (it was a specific personal key), and > `user:add` lost its `PUBKEY` default (it was a specific personal key), and
> `ssh:keygen` inherits that failure because it declares `dependencies: () => > `ssh:keygen` inherits that failure because it declares `dependencies: () =>
+23 -23
View File
@@ -28,8 +28,8 @@ shipped. If you only want to cut a release, jump to
| --------------------- | ----------------------- | -------- | ------------------------ | | --------------------- | ----------------------- | -------- | ------------------------ |
| `packages/nopy` | `@bitsquare/nopy` | `nopy` | CLI | | `packages/nopy` | `@bitsquare/nopy` | `nopy` | CLI |
| `packages/keyman` | `@bitsquare/keyman` | `keyman` | CLI | | `packages/keyman` | `@bitsquare/keyman` | `keyman` | CLI |
| `packages/nopy-cube` | `@bitsquare/nopy-cube` | — | library (cube authoring) | | `packages/nopy-cubes` | `@bitsquare/nopy-cubes` | — | library (cube authoring) |
| `packages/cubes-core` | `@bitsquare/cubes-core` | — | cube bundle (no build) | | `packages/nopy-cubes-core` | `@bitsquare/nopy-cubes-core` | — | cube bundle (no build) |
All are ESM and declare `engines.node >= 22`. The two CLIs expose a single All are ESM and declare `engines.node >= 22`. The two CLIs expose a single
executable through `bin`, so `npm install -g` puts `nopy` / `keyman` on the executable through `bin`, so `npm install -g` puts `nopy` / `keyman` on the
@@ -37,7 +37,7 @@ executable through `bin`, so `npm install -g` puts `nopy` / `keyman` on the
The tarball contents are pinned by `files` — for the three TypeScript packages The tarball contents are pinned by `files` — for the three TypeScript packages
that is `["dist", "README.md", "LICENSE"]`, so sources and tests are not shipped. that is `["dist", "README.md", "LICENSE"]`, so sources and tests are not shipped.
`cubes-core` ships `["cubes", "!cubes/**/*.log", "README.md", "LICENSE"]`: the `nopy-cubes-core` ships `["cubes", "!cubes/**/*.log", "README.md", "LICENSE"]`: the
negation matters, because a cube that has been run leaves a `pyinfra-debug.log` negation matters, because a cube that has been run leaves a `pyinfra-debug.log`
next to its `deploy.py`, and `.gitignore` does not filter an npm tarball. next to its `deploy.py`, and `.gitignore` does not filter an npm tarball.
`publishConfig.access: "public"` is what makes a scoped package publishable to `publishConfig.access: "public"` is what makes a scoped package publishable to
@@ -45,7 +45,7 @@ npmjs without an extra flag; the workflows pass `--access public` anyway.
### Dependencies between them ### Dependencies between them
`keyman` stands alone. `nopy` and `cubes-core` both depend on `nopy-cube` through `keyman` stands alone. `nopy` and `nopy-cubes-core` both depend on `nopy-cubes` through
`workspace:*`, which drives three rules the rest of this document keeps coming `workspace:*`, which drives three rules the rest of this document keeps coming
back to: back to:
@@ -55,7 +55,7 @@ back to:
through verbatim and the install fails with `EUNSUPPORTEDPROTOCOL`. `pnpm through verbatim and the install fails with `EUNSUPPORTEDPROTOCOL`. `pnpm
pack` and `pnpm publish` substitute the concrete version at pack time. Both pack` and `pnpm publish` substitute the concrete version at pack time. Both
workflows use `pnpm publish --ignore-scripts --no-git-checks`. workflows use `pnpm publish --ignore-scripts --no-git-checks`.
2. **`nopy-cube` publishes before anything that depends on it.** 2. **`nopy-cubes` publishes before anything that depends on it.**
`node scripts/publish-order.mjs` prints the publishable directories in `node scripts/publish-order.mjs` prints the publishable directories in
dependency order — note that plain alphabetical `packages/*/` gets this dependency order — note that plain alphabetical `packages/*/` gets this
backwards, putting `nopy` first. backwards, putting `nopy` first.
@@ -124,7 +124,7 @@ The publish step is **two passes** over `node scripts/publish-order.mjs`: the
first stamps the snapshot version into every manifest with `npm pkg set`, the first stamps the snapshot version into every manifest with `npm pkg set`, the
second publishes. They cannot be one loop — `pnpm publish` reads a linked second publishes. They cannot be one loop — `pnpm publish` reads a linked
package's version out of its manifest at pack time, so stamping and publishing package's version out of its manifest at pack time, so stamping and publishing
one package at a time would bake the *old* `nopy-cube` version into `nopy`'s one package at a time would bake the *old* `nopy-cubes` version into `nopy`'s
tarball. tarball.
### `release.yml` ### `release.yml`
@@ -144,7 +144,7 @@ the whole gate.
*Check linked deps are released* asks npmjs whether every `workspace:` dependency *Check linked deps are released* asks npmjs whether every `workspace:` dependency
of the package being released already exists at the version pnpm is about to of the package being released already exists at the version pnpm is about to
bake in (`scripts/linked-deps.mjs``npm view`). Tagging `nopy-v1.3.0` while bake in (`scripts/linked-deps.mjs``npm view`). Tagging `nopy-v1.3.0` while
`@bitsquare/nopy-cube@1.1.0` is still unpublished would otherwise ship a tarball `@bitsquare/nopy-cubes@1.1.0` is still unpublished would otherwise ship a tarball
nobody can install, and npmjs only lets you unpublish for 72 hours. The check is nobody can install, and npmjs only lets you unpublish for 72 hours. The check is
npmjs-only: it runs before any credentials are written, and npmjs is the registry npmjs-only: it runs before any credentials are written, and npmjs is the registry
where the mistake is permanent. where the mistake is permanent.
@@ -253,26 +253,26 @@ the same way:
```sh ```sh
git tag nopy-v1.2.0 git tag nopy-v1.2.0
git tag keyman-v1.2.0 git tag keyman-v1.2.0
git tag nopy-cube-v1.2.0 git tag nopy-cubes-v1.2.0
git tag cubes-core-v1.2.0 git tag nopy-cubes-core-v1.2.0
``` ```
### Ordering when more than one package changed ### Ordering when more than one package changed
Tags are independent, but the dependency graph is not. If a release touches Tags are independent, but the dependency graph is not. If a release touches
`nopy-cube` *and* something that depends on it, release them in this order, `nopy-cubes` *and* something that depends on it, release them in this order,
waiting for each run to go green: waiting for each run to go green:
``` ```
nopy-cube → nopy, cubes-core (these two are independent of each other) nopy-cubes → nopy, nopy-cubes-core (these two are independent of each other)
``` ```
Release `nopy` first and the run stops at the *check linked deps* step, telling Release `nopy` first and the run stops at the *check linked deps* step, telling
you the `nopy-cube` version it wanted is not on npmjs. That is the guard working; you the `nopy-cubes` version it wanted is not on npmjs. That is the guard working;
release `nopy-cube`, then re-tag. `node scripts/publish-order.mjs` prints the release `nopy-cubes`, then re-tag. `node scripts/publish-order.mjs` prints the
order if you would rather not reason about it. order if you would rather not reason about it.
Bumping `nopy-cube` means bumping the packages that depend on it in the same Bumping `nopy-cubes` means bumping the packages that depend on it in the same
change — the `workspace:*` range resolves to whatever version is in the workspace change — the `workspace:*` range resolves to whatever version is in the workspace
at pack time, so their next release picks it up whether or not you meant it to. at pack time, so their next release picks it up whether or not you meant it to.
@@ -387,12 +387,12 @@ npm install -g @bitsquare/nopy @bitsquare/keyman
``` ```
The other two go into a project. A cube bundle is a dev dependency of whatever The other two go into a project. A cube bundle is a dev dependency of whatever
repo describes your infrastructure; `nopy-cube` is only needed if you are writing repo describes your infrastructure; `nopy-cubes` is only needed if you are writing
cubes of your own: cubes of your own:
```sh ```sh
pnpm add -D @bitsquare/cubes-core # then name it in .nopyrc.json cubePackages pnpm add -D @bitsquare/nopy-cubes-core # then name it in .nopyrc.json cubePackages
pnpm add -D @bitsquare/nopy-cube zod # authoring your own manifests pnpm add -D @bitsquare/nopy-cubes zod # authoring your own manifests
``` ```
From the Gitea registry, which holds every snapshot plus a mirror of every From the Gitea registry, which holds every snapshot plus a mirror of every
@@ -525,9 +525,9 @@ pnpm run try:snapshot -- --keep # keep the directory
`scripts/try-snapshot.mjs` builds a throwaway project in a temp directory, `scripts/try-snapshot.mjs` builds a throwaway project in a temp directory,
points the `@bitsquare` scope at the registry, installs `@bitsquare/nopy` and points the `@bitsquare` scope at the registry, installs `@bitsquare/nopy` and
`@bitsquare/cubes-core` at that tag, and then: `@bitsquare/nopy-cubes-core` at that tag, and then:
- asserts the installed `nopy` declares a **concrete** `nopy-cube` version - asserts the installed `nopy` declares a **concrete** `nopy-cubes` version
rather than a leaked `workspace:*` range; rather than a leaked `workspace:*` range;
- prints the three resolved versions, so you can see which commit you are on; - prints the three resolved versions, so you can see which commit you are on;
- runs `nopy --version`; - runs `nopy --version`;
@@ -662,13 +662,13 @@ Use **npm**, not pnpm: npm is the one that rejects a leaked `workspace:` range,
so a clean install here is the real proof. so a clean install here is the real proof.
```sh ```sh
pnpm --filter @bitsquare/nopy-cube pack --pack-destination /tmp/tgz pnpm --filter @bitsquare/nopy-cubes pack --pack-destination /tmp/tgz
pnpm --filter @bitsquare/nopy pack --pack-destination /tmp/tgz pnpm --filter @bitsquare/nopy pack --pack-destination /tmp/tgz
pnpm --filter @bitsquare/cubes-core pack --pack-destination /tmp/tgz pnpm --filter @bitsquare/nopy-cubes-core pack --pack-destination /tmp/tgz
mkdir /tmp/try && cd /tmp/try && npm init -y mkdir /tmp/try && cd /tmp/try && npm init -y
npm install /tmp/tgz/*.tgz npm install /tmp/tgz/*.tgz
echo '{"hosts":["h"],"cubePackages":["@bitsquare/cubes-core"]}' > .nopyrc.json echo '{"hosts":["h"],"cubePackages":["@bitsquare/nopy-cubes-core"]}' > .nopyrc.json
./node_modules/.bin/nopy install -l session.json -P -D ./node_modules/.bin/nopy install -l session.json -P -D
``` ```
@@ -713,7 +713,7 @@ pnpm run registry:status
| Snapshot workflow green, nothing installable | Snapshots are only on Gitea and only under `@main`. Point the scope at the Gitea registry. | | Snapshot workflow green, nothing installable | Snapshots are only on Gitea and only under `@main`. Point the scope at the Gitea registry. |
| The release workflow did not trigger | The tag must match `*-v*` and must be pushed (`git push origin <tag>`), not just created. | | The release workflow did not trigger | The tag must match `*-v*` and must be pushed (`git push origin <tag>`), not just created. |
| `EUNSUPPORTEDPROTOCOL` / `Unsupported URL Type "workspace:"` on install | A `workspace:` range reached a tarball — something published with `npm publish` instead of `pnpm publish`. `verify-pack.mjs` exists to catch this before it ships. | | `EUNSUPPORTEDPROTOCOL` / `Unsupported URL Type "workspace:"` on install | A `workspace:` range reached a tarball — something published with `npm publish` instead of `pnpm publish`. `verify-pack.mjs` exists to catch this before it ships. |
| `... is not published yet on npmjs` before the gate runs | Releasing a package before its `nopy-cube` dependency. Tag and release `nopy-cube` first, then re-tag. | | `... is not published yet on npmjs` before the gate runs | Releasing a package before its `nopy-cubes` dependency. Tag and release `nopy-cubes` first, then re-tag. |
| `verify-pack.mjs` fails locally with a build error | `pnpm pack` runs `prepack`, so a broken build fails the check. Fix the build; there is no skip flag. | | `verify-pack.mjs` fails locally with a build error | `pnpm pack` runs `prepack`, so a broken build fails the check. Fix the build; there is no skip flag. |
## Recovering from a bad publish ## Recovering from a bad publish
+1 -1
View File
@@ -28,7 +28,7 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.5.5", "@biomejs/biome": "^2.5.5",
"@bitsquare/cubes-core": "workspace:*", "@bitsquare/nopy-cubes-core": "workspace:*",
"@logtape/logtape": "^2.2.4", "@logtape/logtape": "^2.2.4",
"@types/node": "^26.1.1", "@types/node": "^26.1.1",
"simple-git-hooks": "^2.13.1", "simple-git-hooks": "^2.13.1",
@@ -1,4 +1,4 @@
# @bitsquare/cubes-core # @bitsquare/nopy-cubes-core
The core cube bundle for [nopy](https://www.npmjs.com/package/@bitsquare/nopy): The core cube bundle for [nopy](https://www.npmjs.com/package/@bitsquare/nopy):
base packages, users, SSH, firewalling, networking, web serving and runtimes. base packages, users, SSH, firewalling, networking, web serving and runtimes.
@@ -6,7 +6,7 @@ base packages, users, SSH, firewalling, networking, web serving and runtimes.
## Install ## Install
```sh ```sh
pnpm add -D @bitsquare/cubes-core pnpm add -D @bitsquare/nopy-cubes-core
``` ```
Then name it in `.nopyrc.json`: Then name it in `.nopyrc.json`:
@@ -14,7 +14,7 @@ Then name it in `.nopyrc.json`:
```json ```json
{ {
"hosts": ["web-1"], "hosts": ["web-1"],
"cubePackages": ["@bitsquare/cubes-core"] "cubePackages": ["@bitsquare/nopy-cubes-core"]
} }
``` ```
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
export default Manifest({ export default Manifest({
id: 'admin:cockpit', id: 'admin:cockpit',
@@ -1,4 +1,4 @@
import { Manifest, uniqid } from '@bitsquare/nopy-cube'; import { Manifest, uniqid } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
/** /**
@@ -12,7 +12,7 @@ Configures system keyboard layout permanently by updating `/etc/default/keyboard
## Usage ## Usage
```javascript ```javascript
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
export default Manifest({ export default Manifest({
name: 'My Host Setup', name: 'My Host Setup',
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
export default Manifest({ export default Manifest({
id: 'armor:fail2ban', id: 'armor:fail2ban',
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
// [agnt://cogen/cogen/network-wifi-connection-1]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"} // [agnt://cogen/cogen/network-wifi-connection-1]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"}
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest, uniqid } from '@bitsquare/nopy-cube'; import { Manifest, uniqid } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
// [agnt://cogen/cogen/user-edit-1]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"} // [agnt://cogen/cogen/user-edit-1]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"}
@@ -1,5 +1,5 @@
{ {
"name": "@bitsquare/cubes-core", "name": "@bitsquare/nopy-cubes-core",
"version": "0.5.0", "version": "0.5.0",
"description": "The core nopy cube bundle: apt, users, ssh, networking, services and runtimes.", "description": "The core nopy cube bundle: apt, users, ssh, networking, services and runtimes.",
"keywords": [ "keywords": [
@@ -15,9 +15,9 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://gitea.bitsquare.dev/BitSquare/ansiblings.git", "url": "git+https://gitea.bitsquare.dev/BitSquare/ansiblings.git",
"directory": "packages/cubes-core" "directory": "packages/nopy-cubes-core"
}, },
"homepage": "https://gitea.bitsquare.dev/BitSquare/ansiblings/src/branch/main/packages/cubes-core", "homepage": "https://gitea.bitsquare.dev/BitSquare/ansiblings/src/branch/main/packages/nopy-cubes-core",
"bugs": { "bugs": {
"url": "https://gitea.bitsquare.dev/BitSquare/ansiblings/issues" "url": "https://gitea.bitsquare.dev/BitSquare/ansiblings/issues"
}, },
@@ -39,7 +39,7 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@bitsquare/nopy-cube": "workspace:*", "@bitsquare/nopy-cubes": "workspace:*",
"zod": "^4.4.3" "zod": "^4.4.3"
} }
} }
@@ -1,4 +1,4 @@
# @bitsquare/nopy-cube # @bitsquare/nopy-cubes
The authoring surface for [nopy](https://www.npmjs.com/package/@bitsquare/nopy) The authoring surface for [nopy](https://www.npmjs.com/package/@bitsquare/nopy)
cubes — the `Manifest` factory, the `Cube` class, and the types around them. cubes — the `Manifest` factory, the `Cube` class, and the types around them.
@@ -12,7 +12,7 @@ re-exports everything here, so a manifest that already imports from
## Install ## Install
```sh ```sh
pnpm add @bitsquare/nopy-cube zod pnpm add @bitsquare/nopy-cubes zod
``` ```
`zod` is a **peer dependency** on purpose: the manifest, the schema it builds and `zod` is a **peer dependency** on purpose: the manifest, the schema it builds and
@@ -22,7 +22,7 @@ the `Manifest` factory should all see the same copy.
```js ```js
// cubes/net/tailscale/manifest.mjs // cubes/net/tailscale/manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -1,5 +1,5 @@
{ {
"name": "@bitsquare/nopy-cube", "name": "@bitsquare/nopy-cubes",
"version": "0.5.0", "version": "0.5.0",
"description": "Authoring types for nopy cubes: the Manifest factory and the Cube contract.", "description": "Authoring types for nopy cubes: the Manifest factory and the Cube contract.",
"keywords": [ "keywords": [
@@ -14,9 +14,9 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://gitea.bitsquare.dev/BitSquare/ansiblings.git", "url": "git+https://gitea.bitsquare.dev/BitSquare/ansiblings.git",
"directory": "packages/nopy-cube" "directory": "packages/nopy-cubes"
}, },
"homepage": "https://gitea.bitsquare.dev/BitSquare/ansiblings/src/branch/main/packages/nopy-cube", "homepage": "https://gitea.bitsquare.dev/BitSquare/ansiblings/src/branch/main/packages/nopy-cubes",
"bugs": { "bugs": {
"url": "https://gitea.bitsquare.dev/BitSquare/ansiblings/issues" "url": "https://gitea.bitsquare.dev/BitSquare/ansiblings/issues"
}, },
@@ -1,5 +1,5 @@
/** /**
* @bitsquare/nopy-cube the authoring surface for nopy cubes. * @bitsquare/nopy-cubes the authoring surface for nopy cubes.
* *
* Everything a `manifest.mjs` needs and nothing else: no CLI, no prompts, no * Everything a `manifest.mjs` needs and nothing else: no CLI, no prompts, no
* process spawning. `@bitsquare/nopy` re-exports all of it, so a manifest can * process spawning. `@bitsquare/nopy` re-exports all of it, so a manifest can
+5 -5
View File
@@ -164,7 +164,7 @@ Uses `.nopyrc.json` files (project-level or home directory) containing:
{ {
"hosts": ["host1.example.com", "host2.example.com"], "hosts": ["host1.example.com", "host2.example.com"],
"cubeDirs": ["./cubes", "../shared-cubes"], "cubeDirs": ["./cubes", "../shared-cubes"],
"cubePackages": ["@bitsquare/cubes-core"], "cubePackages": ["@bitsquare/nopy-cubes-core"],
"env": { "env": {
"SHARED_VAR": "value" "SHARED_VAR": "value"
}, },
@@ -314,11 +314,11 @@ A cube package is an ordinary npm package that ships its cubes in a `cubes/` dir
Install it and name it — nothing needs linking or copying: Install it and name it — nothing needs linking or copying:
```sh ```sh
pnpm add -D @bitsquare/cubes-core pnpm add -D @bitsquare/nopy-cubes-core
``` ```
```json ```json
{ "cubePackages": ["@bitsquare/cubes-core"] } { "cubePackages": ["@bitsquare/nopy-cubes-core"] }
``` ```
Naming a package is a statement that cubes are expected from it, so anything wrong is an error that aborts the run rather than a silent skip: the package is not installed, it has neither a `cubes/` directory nor a `nopy.cubes` override, its `nopy.cubes` is malformed, or an entry points at a directory that does not exist or lies outside the package. Naming a package is a statement that cubes are expected from it, so anything wrong is an error that aborts the run rather than a silent skip: the package is not installed, it has neither a `cubes/` directory nor a `nopy.cubes` override, its `nopy.cubes` is malformed, or an entry points at a directory that does not exist or lies outside the package.
@@ -341,11 +341,11 @@ The cubes live in a separate bundle, installed into whichever project describes
your infrastructure and named in its `.nopyrc.json`: your infrastructure and named in its `.nopyrc.json`:
```bash ```bash
pnpm add -D @bitsquare/cubes-core pnpm add -D @bitsquare/nopy-cubes-core
``` ```
```json ```json
{ "hosts": ["your-host"], "cubePackages": ["@bitsquare/cubes-core"] } { "hosts": ["your-host"], "cubePackages": ["@bitsquare/nopy-cubes-core"] }
``` ```
#### Channels #### Channels
+14 -14
View File
@@ -1,7 +1,7 @@
# Nopy API Reference # Nopy API Reference
The public surface of **`@bitsquare/nopy`** (the CLI and its library exports) and The public surface of **`@bitsquare/nopy`** (the CLI and its library exports) and
of **`@bitsquare/nopy-cube`** (the authoring package a `manifest.mjs` imports). of **`@bitsquare/nopy-cubes`** (the authoring package a `manifest.mjs` imports).
Everything below was checked against the source. Where the code does something a Everything below was checked against the source. Where the code does something a
reader would not expect — a field that is always empty, a function nothing calls reader would not expect — a field that is always empty, a function nothing calls
@@ -10,14 +10,14 @@ reader would not expect — a field that is always empty, a function nothing cal
If you are writing cubes rather than calling nopy from code, you want If you are writing cubes rather than calling nopy from code, you want
[CUBE-BUNDLES.md](CUBE-BUNDLES.md) and [HOOKS.md](HOOKS.md); only the [CUBE-BUNDLES.md](CUBE-BUNDLES.md) and [HOOKS.md](HOOKS.md); only the
[Authoring API](#authoring-api-bitsquarenopy-cube) section here applies to you. [Authoring API](#authoring-api-bitsquarenopy-cubes) section here applies to you.
--- ---
## Table of Contents ## Table of Contents
- [Two packages](#two-packages) - [Two packages](#two-packages)
- [Authoring API (`@bitsquare/nopy-cube`)](#authoring-api-bitsquarenopy-cube) - [Authoring API (`@bitsquare/nopy-cubes`)](#authoring-api-bitsquarenopy-cubes)
- [Main Module](#main-module) - [Main Module](#main-module)
- [Cubes Module](#cubes-module) - [Cubes Module](#cubes-module)
- [Variables Module](#variables-module) - [Variables Module](#variables-module)
@@ -38,15 +38,15 @@ If you are writing cubes rather than calling nopy from code, you want
| Package | Contains | Depends on | | Package | Contains | Depends on |
| --- | --- | --- | | --- | --- | --- |
| `@bitsquare/nopy-cube` | `Manifest`, `Cube`, `Hook`, `uniqid`, the zod helpers | zod (peer) | | `@bitsquare/nopy-cubes` | `Manifest`, `Cube`, `Hook`, `uniqid`, the zod helpers | zod (peer) |
| `@bitsquare/nopy` | the CLI, the loader, config, sessions, execution | `@bitsquare/nopy-cube` | | `@bitsquare/nopy` | the CLI, the loader, config, sessions, execution | `@bitsquare/nopy-cubes` |
A `manifest.mjs` should import from **`@bitsquare/nopy-cube`**: it is types and a A `manifest.mjs` should import from **`@bitsquare/nopy-cubes`**: it is types and a
factory with no CLI, no prompts and no process spawning, so a cube bundle can factory with no CLI, no prompts and no process spawning, so a cube bundle can
depend on it without pulling the whole tool into its dependency graph. depend on it without pulling the whole tool into its dependency graph.
```javascript ```javascript
import { Manifest } from '@bitsquare/nopy-cube'; // prefer this import { Manifest } from '@bitsquare/nopy-cubes'; // prefer this
import { cubes } from '@bitsquare/nopy'; // cubes.Manifest — still supported import { cubes } from '@bitsquare/nopy'; // cubes.Manifest — still supported
``` ```
@@ -57,7 +57,7 @@ compatibility; it also carries a `cubes.load` alias for `loadCubes`.
--- ---
## Authoring API (`@bitsquare/nopy-cube`) ## Authoring API (`@bitsquare/nopy-cubes`)
### `Manifest(opts)` ### `Manifest(opts)`
@@ -65,7 +65,7 @@ Factory for a cube manifest. `name` is the only required option; everything else
is filled in. is filled in.
```javascript ```javascript
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -85,7 +85,7 @@ Defaults applied by the factory: `id: ''`, `schema: z.object({})`,
`createManifest` and `manifest` are exported as identical aliases. `createManifest` and `manifest` are exported as identical aliases.
`ManifestFactory` is a third alias, marked `@deprecated` — and it is not `ManifestFactory` is a third alias, marked `@deprecated` — and it is not
re-exported through `@bitsquare/nopy`, so it is only reachable from re-exported through `@bitsquare/nopy`, so it is only reachable from
`@bitsquare/nopy-cube` directly. `@bitsquare/nopy-cubes` directly.
#### `Manifest<Schema>` (interface) #### `Manifest<Schema>` (interface)
@@ -336,7 +336,7 @@ for callers that only want to display what was found.
`loadCubes()` also registers the resolve hook (`cubes/resolve-hook.mjs`) before `loadCubes()` also registers the resolve hook (`cubes/resolve-hook.mjs`) before
importing anything. The hook tries ordinary Node resolution first and only on importing anything. The hook tries ordinary Node resolution first and only on
failure falls back to resolving `@bitsquare/nopy-cube`, `@bitsquare/nopy` and failure falls back to resolving `@bitsquare/nopy-cubes`, `@bitsquare/nopy` and
`zod` from the running CLI's own `node_modules` — so a hand-written cube in a `zod` from the running CLI's own `node_modules` — so a hand-written cube in a
directory with no `node_modules` loads, while a cube shipping its own zod keeps directory with no `node_modules` loads, while a cube shipping its own zod keeps
it. Registration is best-effort: it is a convenience, never load-bearing. it. Registration is best-effort: it is a convenience, never load-bearing.
@@ -811,7 +811,7 @@ type ResolutionConfig = { [K in keyof NopyConfig]?: ResolutionStrategy };
### `CubePackageRef` ### `CubePackageRef`
A package named in `cubePackages`, paired with where it was named. In the file an A package named in `cubePackages`, paired with where it was named. In the file an
entry is just a string (`"@bitsquare/cubes-core"`); `loadConfig()` normalises it. entry is just a string (`"@bitsquare/nopy-cubes-core"`); `loadConfig()` normalises it.
```typescript ```typescript
interface CubePackageRef { interface CubePackageRef {
@@ -848,7 +848,7 @@ objects deep-merge, primitives are replaced. `override` replaces outright.
```json ```json
{ {
"hosts": ["local-host"], "hosts": ["local-host"],
"cubePackages": ["@bitsquare/cubes-core"], "cubePackages": ["@bitsquare/nopy-cubes-core"],
"resolution": { "hosts": "override" } "resolution": { "hosts": "override" }
} }
``` ```
@@ -1111,7 +1111,7 @@ working directory. The prefixed forms `<name>.manifest.mjs` and
```javascript ```javascript
// manifest.mjs // manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
+9 -9
View File
@@ -6,7 +6,7 @@ what changes once a cube lives in `node_modules` instead of in your own tree.
If you only want to *use* a published bundle, you need one line of config: If you only want to *use* a published bundle, you need one line of config:
```json ```json
{ "cubePackages": ["@bitsquare/cubes-core"] } { "cubePackages": ["@bitsquare/nopy-cubes-core"] }
``` ```
The rest of this document is for writing one. The rest of this document is for writing one.
@@ -39,7 +39,7 @@ and imports each `manifest.mjs` directly.
└── deploy.py └── deploy.py
``` ```
`@bitsquare/cubes-core` in this repository is the worked example, and is consumed `@bitsquare/nopy-cubes-core` in this repository is the worked example, and is consumed
by this repository through exactly the mechanism described here — it is not by this repository through exactly the mechanism described here — it is not
special-cased. special-cased.
@@ -53,7 +53,7 @@ special-cased.
"files": ["cubes", "!cubes/**/*.log", "README.md", "LICENSE"], "files": ["cubes", "!cubes/**/*.log", "README.md", "LICENSE"],
"publishConfig": { "access": "public" }, "publishConfig": { "access": "public" },
"dependencies": { "dependencies": {
"@bitsquare/nopy-cube": "^1.0.0", "@bitsquare/nopy-cubes": "^1.0.0",
"zod": "^4.4.3" "zod": "^4.4.3"
} }
} }
@@ -86,7 +86,7 @@ not behave the way you expect.
leaves a `pyinfra-debug.log` next to its `deploy.py`, and `.gitignore` has no leaves a `pyinfra-debug.log` next to its `deploy.py`, and `.gitignore` has no
effect on what npm packs. Check with `npm pack --dry-run` before publishing. effect on what npm packs. Check with `npm pack --dry-run` before publishing.
**Dependencies** are `@bitsquare/nopy-cube` and `zod`, both real dependencies **Dependencies** are `@bitsquare/nopy-cubes` and `zod`, both real dependencies
rather than peers — a bundle is a leaf, and the copies it gets are the copies its rather than peers — a bundle is a leaf, and the copies it gets are the copies its
manifests use. Do **not** depend on `@bitsquare/nopy`: the CLI is what installs manifests use. Do **not** depend on `@bitsquare/nopy`: the CLI is what installs
your bundle, not the other way round, and depending on it invites two copies of your bundle, not the other way round, and depending on it invites two copies of
@@ -101,7 +101,7 @@ directory as its working directory.
```javascript ```javascript
// cubes/nginx/manifest.mjs // cubes/nginx/manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube'; import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
export default Manifest({ export default Manifest({
@@ -127,7 +127,7 @@ SERVER_NAME = host.data.SERVER_NAME
apt.packages(name='Install nginx', packages=['nginx'], _sudo=True) apt.packages(name='Install nginx', packages=['nginx'], _sudo=True)
``` ```
Import **`@bitsquare/nopy-cube`**, not `@bitsquare/nopy`. It is types and a Import **`@bitsquare/nopy-cubes`**, not `@bitsquare/nopy`. It is types and a
factory with zod as its only peer — no CLI, no prompts, no process spawning — so factory with zod as its only peer — no CLI, no prompts, no process spawning — so
your bundle stays a leaf. (`@bitsquare/nopy` re-exports the same surface as your bundle stays a leaf. (`@bitsquare/nopy` re-exports the same surface as
`cubes.Manifest`, which is what older manifests use. It still works; it just `cubes.Manifest`, which is what older manifests use. It still works; it just
@@ -214,10 +214,10 @@ the cube scan itself.
**How a manifest finds its imports.** Ordinary Node resolution, from the **How a manifest finds its imports.** Ordinary Node resolution, from the
manifest's own directory. An installed bundle has its own `node_modules` with manifest's own directory. An installed bundle has its own `node_modules` with
`@bitsquare/nopy-cube` and `zod` in it, so this just works. A hand-written cube `@bitsquare/nopy-cubes` and `zod` in it, so this just works. A hand-written cube
sitting in a directory with no `node_modules` would historically fail with sitting in a directory with no `node_modules` would historically fail with
`ERR_MODULE_NOT_FOUND`; nopy now registers a resolve hook that catches exactly `ERR_MODULE_NOT_FOUND`; nopy now registers a resolve hook that catches exactly
that case and falls back to resolving `@bitsquare/nopy-cube`, `@bitsquare/nopy` that case and falls back to resolving `@bitsquare/nopy-cubes`, `@bitsquare/nopy`
and `zod` from the running CLI. Normal resolution is always tried first, so a and `zod` from the running CLI. Normal resolution is always tried first, so a
cube that ships its own zod keeps it. Treat the hook as a convenience for local cube that ships its own zod keeps it. Treat the hook as a convenience for local
cubes — a published bundle must declare its dependencies properly. cubes — a published bundle must declare its dependencies properly.
@@ -272,6 +272,6 @@ For how this repository releases its own packages, see
| `'…' does not exist in …` | A `nopy.cubes` entry pointing at a directory the tarball does not contain. | | `'…' 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. | | `'…' 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. | | `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. | | `ERR_MODULE_NOT_FOUND` for `zod` or `@bitsquare/nopy-cubes` | The bundle did not declare them as dependencies. The resolve-hook fallback covers loose local cubes, not published packages. |
| `Invalid manifest in …: 'secrets' names X, which is not in the schema` | A `secrets` entry with no matching schema key — usually a typo or a renamed field. | | `Invalid manifest in …: 'secrets' names X, which is not in the schema` | A `secrets` entry with no matching schema key — usually a typo or a renamed field. |
| Cubes work linked, fail installed | Almost always a write into the cube's own directory, or a file missing from `files`. | | Cubes work linked, fail installed | Almost always a write into the cube's own directory, or a file missing from `files`. |
+55 -55
View File
@@ -3,7 +3,7 @@
Status: **All six phases have landed. This document is now a record, not a plan.** Status: **All six phases have landed. This document is now a record, not a plan.**
The one thing still unproven is the publish lane against a real registry — see The one thing still unproven is the publish lane against a real registry — see
*Risks*. *Risks*.
`cubePackages` resolves and loads end to end, `@bitsquare/nopy-cube` exists and `cubePackages` resolves and loads end to end, `@bitsquare/nopy-cubes` exists and
the publish lane can ship a linked package. What is missing is a bundle to point the publish lane can ship a linked package. What is missing is a bundle to point
`cubePackages` at. `cubePackages` at.
@@ -35,8 +35,8 @@ and have its cubes show up in `nopy` alongside local ones.
| Duplicate cube ids across sources | **Hard error.** No precedence, no shadowing. Mitigation is a good error message, not a fallback. | | Duplicate cube ids across sources | **Hard error.** No precedence, no shadowing. Mitigation is a good error message, not a fallback. |
| Id format | Unchanged, flat. The id is the session key (`dependencies.ts:135`); changing it breaks `--repeat-last` and `--history`. | | Id format | Unchanged, flat. The id is the session key (`dependencies.ts:135`); changing it breaks `--repeat-last` and `--history`. |
| Discovery | Explicit `cubePackages` list in `.nopyrc.json`. | | Discovery | Explicit `cubePackages` list in `.nopyrc.json`. |
| Migrate in-repo `cubes/` | Yes — `packages/cubes-core`, as the proof of concept. | | Migrate in-repo `cubes/` | Yes — `packages/nopy-cubes-core`, as the proof of concept. |
| Split an authoring package (`@bitsquare/nopy-cube`) | **Yes.** Bundles take a regular dependency on it; `@bitsquare/nopy` re-exports it for backwards compatibility. See *Phase 4*. | | Split an authoring package (`@bitsquare/nopy-cubes`) | **Yes.** Bundles take a regular dependency on it; `@bitsquare/nopy` re-exports it for backwards compatibility. See *Phase 4*. |
## Current state ## Current state
@@ -142,9 +142,9 @@ A cube bundle is an npm package with a `nopy` field:
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"files": ["cubes", "README.md", "LICENSE"], "files": ["cubes", "README.md", "LICENSE"],
"keywords": ["nopy", "nopy-cubes", "pyinfra"], "keywords": ["nopy", "nopy-cubess", "pyinfra"],
"dependencies": { "dependencies": {
"@bitsquare/nopy-cube": "^1.0.0", "@bitsquare/nopy-cubes": "^1.0.0",
"zod": "^4.4.3" "zod": "^4.4.3"
}, },
"publishConfig": { "access": "public" } "publishConfig": { "access": "public" }
@@ -164,8 +164,8 @@ Rules:
back. Finding no cube directory at all is still an error, not a silent skip: 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. listing a package means the user expects cubes from it.
- Both dependencies are **regular dependencies, not peers**, and both are - Both dependencies are **regular dependencies, not peers**, and both are
load-bearing: a manifest imports `Manifest` from `@bitsquare/nopy-cube` and `z` load-bearing: a manifest imports `Manifest` from `@bitsquare/nopy-cubes` and `z`
from `zod`. `@bitsquare/nopy-cube` peer-depends on zod, so the bundle's copy is from `zod`. `@bitsquare/nopy-cubes` peer-depends on zod, so the bundle's copy is
the one everybody uses — see Phase 4. the one everybody uses — see Phase 4.
- The package needs no `exports` entry for this to work — resolution reads - The package needs no `exports` entry for this to work — resolution reads
`package.json` off disk (Phase 2), so the `exports` map is irrelevant. `package.json` off disk (Phase 2), so the `exports` map is irrelevant.
@@ -306,7 +306,7 @@ The duplicate error carries both sources and is order-independent (Phase 0.2):
``` ```
Duplicate cube id 'apt:essentials' from 2 sources: Duplicate cube id 'apt:essentials' from 2 sources:
package @bitsquare/cubes-core /…/node_modules/@bitsquare/cubes-core/cubes/apt/essentials package @bitsquare/nopy-cubes-core /…/node_modules/@bitsquare/nopy-cubes-core/cubes/apt/essentials
directory /repo/packages/nopy/cubes/apt/essentials directory /repo/packages/nopy/cubes/apt/essentials
Rename one of them, or remove a source from .nopyrc.json. Rename one of them, or remove a source from .nopyrc.json.
``` ```
@@ -317,7 +317,7 @@ claim the same id they are mutually exclusive, and the fix is upstream.
Surface the source in the interactive picker and in `--json` output so a user can Surface the source in the interactive picker and in `--json` output so a user can
see where a cube came from before running it. see where a cube came from before running it.
## Phase 4 — `@bitsquare/nopy-cube`, the authoring package — **done** ## Phase 4 — `@bitsquare/nopy-cubes`, the authoring package — **done**
The problem: a manifest does `import { cubes } from '@bitsquare/nopy'`, resolved The problem: a manifest does `import { cubes } from '@bitsquare/nopy'`, resolved
by ordinary Node resolution from the manifest's own directory. From inside by ordinary Node resolution from the manifest's own directory. From inside
@@ -329,7 +329,7 @@ is plain, boring, spec-compliant Node with no loader tricks in the critical path
### The package ### The package
`packages/nopy-cube` — the `Manifest` factory, the `Cube` class, and the types `packages/nopy-cubes` — the `Manifest` factory, the `Cube` class, and the types
from `cubes/types.ts`. No CLI, no `execa`, `inquirer`, `enquirer`, `zx`, or from `cubes/types.ts`. No CLI, no `execa`, `inquirer`, `enquirer`, `zx`, or
`commander`. Today a cube manifest — a file that ships nothing but data — drags `commander`. Today a cube manifest — a file that ships nothing but data — drags
the entire CLI in as a transitive dependency; this makes the authoring surface the entire CLI in as a transitive dependency; this makes the authoring surface
@@ -338,7 +338,7 @@ moves independently of the CLI's.
```json ```json
{ {
"name": "@bitsquare/nopy-cube", "name": "@bitsquare/nopy-cubes",
"version": "1.0.0-alpha0", "version": "1.0.0-alpha0",
"type": "module", "type": "module",
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
@@ -355,7 +355,7 @@ is still the right default.
### Moving `cubes/types.ts` ### Moving `cubes/types.ts`
`@bitsquare/nopy` re-exports everything from `@bitsquare/nopy-cube` — through `@bitsquare/nopy` re-exports everything from `@bitsquare/nopy-cubes` — through
`src/cubes/index.ts` and the `cubes` namespace in `src/nopy.cubes.ts`, both `src/cubes/index.ts` and the `cubes` namespace in `src/nopy.cubes.ts`, both
already coverage-excluded barrels — so `import { cubes } from '@bitsquare/nopy'` already coverage-excluded barrels — so `import { cubes } from '@bitsquare/nopy'`
in every existing manifest keeps working unchanged. Nothing in `cubes/` has to be in every existing manifest keeps working unchanged. Nothing in `cubes/` has to be
@@ -368,7 +368,7 @@ and the alternative is a test-only dependency edge between the packages.
Repo plumbing it took: Repo plumbing it took:
- `tsconfig.base.json`: `"@bitsquare/nopy-cube": ["./packages/nopy-cube/src"]`. - `tsconfig.base.json`: `"@bitsquare/nopy-cubes": ["./packages/nopy-cubes/src"]`.
- Root `tsconfig.json` and `packages/nopy/tsconfig.json`: the project reference. - Root `tsconfig.json` and `packages/nopy/tsconfig.json`: the project reference.
This is the first reference edge in the repo, and it broke the gate This is the first reference edge in the repo, and it broke the gate
immediately: **`tsc --build --noEmit` is not legal once a project has immediately: **`tsc --build --noEmit` is not legal once a project has
@@ -377,12 +377,12 @@ Repo plumbing it took:
`typecheck` script is now plain `tsc --build`. It still fails on a type error, `typecheck` script is now plain `tsc --build`. It still fails on a type error,
and it now also proves the build works; the cost is that it writes `dist`, and it now also proves the build works; the cost is that it writes `dist`,
which is gitignored. which is gitignored.
- `packages/nopy/package.json`: `"@bitsquare/nopy-cube": "workspace:*"`. - `packages/nopy/package.json`: `"@bitsquare/nopy-cubes": "workspace:*"`.
- `packages/nopy/vitest.config.ts`: a `resolve.alias` for `@bitsquare/nopy-cube` - `packages/nopy/vitest.config.ts`: a `resolve.alias` for `@bitsquare/nopy-cubes`
pointing at `../nopy-cube/src/index.ts`. Without it the workspace link pointing at `../nopy-cubes/src/index.ts`. Without it the workspace link
resolves through `exports` to `dist`, so `pnpm test` on a clean checkout would resolves through `exports` to `dist`, so `pnpm test` on a clean checkout would
fail until something had built it, and a stale `dist` would silently be what fail until something had built it, and a stale `dist` would silently be what
the tests ran against. The same config excludes `**/nopy-cube/**` from the tests ran against. The same config excludes `**/nopy-cubes/**` from
coverage — the aliased files were being counted against nopy's thresholds. coverage — the aliased files were being counted against nopy's thresholds.
- A `vitest.config.ts` for the new package with the same thresholds. It sits at - A `vitest.config.ts` for the new package with the same thresholds. It sits at
100 % statements/functions/lines, 91 % branches. 100 % statements/functions/lines, 91 % branches.
@@ -391,14 +391,14 @@ Repo plumbing it took:
This is the part that is easy to miss. `link-workspace-packages` is unset and This is the part that is easy to miss. `link-workspace-packages` is unset and
pnpm 10+ defaults it to `false`, so a plain semver range would resolve pnpm 10+ defaults it to `false`, so a plain semver range would resolve
`@bitsquare/nopy-cube` from the registry instead of linking the workspace copy — `@bitsquare/nopy-cubes` from the registry instead of linking the workspace copy —
the dependency has to use `workspace:*`. the dependency has to use `workspace:*`.
But **both workflows publish with `npm publish`**, and npm does not understand But **both workflows publish with `npm publish`**, and npm does not understand
the `workspace:` protocol. `@bitsquare/nopy` would ship a manifest carrying the `workspace:` protocol. `@bitsquare/nopy` would ship a manifest carrying
`"@bitsquare/nopy-cube": "workspace:*"`, which fails on install with `"@bitsquare/nopy-cubes": "workspace:*"`, which fails on install with
`EUNSUPPORTEDPROTOCOL`. This has never mattered because the two current packages `EUNSUPPORTEDPROTOCOL`. This has never mattered because the two current packages
do not depend on each other; `nopy → nopy-cube` is the first edge, and the PoC do not depend on each other; `nopy → nopy-cubes` is the first edge, and the PoC
bundle in Phase 5 adds a second. bundle in Phase 5 adds a second.
Pick one before publishing anything: Pick one before publishing anything:
@@ -408,13 +408,13 @@ Pick one before publishing anything:
workflows and pulls in pnpm's own lifecycle behaviour. workflows and pulls in pnpm's own lifecycle behaviour.
- **Rewrite the range with `npm pkg set` before publishing**, extending the - **Rewrite the range with `npm pkg set` before publishing**, extending the
pattern `publish-snapshot.yml` already uses for `version`. In `release.yml` one pattern `publish-snapshot.yml` already uses for `version`. In `release.yml` one
package ships at a time, so it pins to whatever version `packages/nopy-cube/package.json` package ships at a time, so it pins to whatever version `packages/nopy-cubes/package.json`
declares at that commit. In `publish-snapshot.yml` the loop needs to become two declares at that commit. In `publish-snapshot.yml` the loop needs to become two
passes — compute every snapshot version first, then publish — so `nopy` can pin passes — compute every snapshot version first, then publish — so `nopy` can pin
the exact `nopy-cube` snapshot from the same run. the exact `nopy-cubes` snapshot from the same run.
**Measured, both directions.** `npm pack` in `packages/nopy` produces a tarball **Measured, both directions.** `npm pack` in `packages/nopy` produces a tarball
whose manifest still reads `"@bitsquare/nopy-cube": "workspace:*"`; `pnpm pack` whose manifest still reads `"@bitsquare/nopy-cubes": "workspace:*"`; `pnpm pack`
produces one that reads `"1.0.0-alpha0"`. So the failure was real and the fix produces one that reads `"1.0.0-alpha0"`. So the failure was real and the fix
works. works.
@@ -431,7 +431,7 @@ locally:
in both workflows. Turns "npm would have shipped a broken manifest" from an in both workflows. Turns "npm would have shipped a broken manifest" from an
install-time surprise into a red run. install-time surprise into a red run.
- **`scripts/publish-order.mjs`** — topologically sorts the publishable packages. - **`scripts/publish-order.mjs`** — topologically sorts the publishable packages.
`packages/*/` alphabetically puts `nopy` ahead of the `nopy-cube` it depends `packages/*/` alphabetically puts `nopy` ahead of the `nopy-cubes` it depends
on; the snapshot workflow now iterates this instead. on; the snapshot workflow now iterates this instead.
- **`scripts/linked-deps.mjs`** — lists a package's workspace links as - **`scripts/linked-deps.mjs`** — lists a package's workspace links as
`<name> <version>`, resolved by package name rather than by directory. `<name> <version>`, resolved by package name rather than by directory.
@@ -440,7 +440,7 @@ locally:
`publish-snapshot.yml` also became two passes over the packages: stamp every `publish-snapshot.yml` also became two passes over the packages: stamp every
version first, then publish. `pnpm publish` substitutes the version the linked version first, then publish. `pnpm publish` substitutes the version the linked
package declares *at pack time*, so `nopy-cube` has to be carrying its snapshot package declares *at pack time*, so `nopy-cubes` has to be carrying its snapshot
version before `nopy` is packed. version before `nopy` is packed.
Still unverified: none of this has run against the Gitea registry. Worth a Still unverified: none of this has run against the Gitea registry. Worth a
@@ -453,7 +453,7 @@ Independent of the split, and worth building anyway — it retires the
where manifests import `@bitsquare/nopy` from a directory that has no link to it. where manifests import `@bitsquare/nopy` from a directory that has no link to it.
With the split, the hook is a convenience rather than load-bearing: bundles With the split, the hook is a convenience rather than load-bearing: bundles
resolve `@bitsquare/nopy-cube` through their own `node_modules` and never reach resolve `@bitsquare/nopy-cubes` through their own `node_modules` and never reach
it. it.
**The gotcha is bigger than CLAUDE.md says: it is two specifiers, not one.** **The gotcha is bigger than CLAUDE.md says: it is two specifiers, not one.**
@@ -495,7 +495,7 @@ Constraints, as built:
now `tsc && cp src/cubes/*.mjs dist/cubes/`. `files` already covers it via the now `tsc && cp src/cubes/*.mjs dist/cubes/`. `files` already covers it via the
`dist` entry. `dist` entry.
- It covers **three** specifiers, not the two the plan named: `zod`, - It covers **three** specifiers, not the two the plan named: `zod`,
`@bitsquare/nopy`, and `@bitsquare/nopy-cube` — a hand-written local cube is `@bitsquare/nopy`, and `@bitsquare/nopy-cubes` — a hand-written local cube is
as entitled to the new authoring package as to the old one. Subpaths count as entitled to the new authoring package as to the old one. Subpaths count
(`@bitsquare/nopy/package.json`), anything else stays a hard failure. (`@bitsquare/nopy/package.json`), anything else stays a hard failure.
@@ -509,80 +509,80 @@ therefore runs each case in a child process, and the first case asserts the
linked, the built loader reads all 22 cubes under `cubes/` with zero errors. The linked, the built loader reads all 22 cubes under `cubes/` with zero errors. The
`ERR_MODULE_NOT_FOUND` gotcha in `CLAUDE.md` is retired. `ERR_MODULE_NOT_FOUND` gotcha in `CLAUDE.md` is retired.
## Phase 5 — proof of concept: `packages/cubes-core` — **done** ## Phase 5 — proof of concept: `packages/nopy-cubes-core` — **done**
Depends on Phase 4 shipping first — the bundle cannot declare Depends on Phase 4 shipping first — the bundle cannot declare
`@bitsquare/nopy-cube` as a dependency until it exists, and the publish-lane fix `@bitsquare/nopy-cubes` as a dependency until it exists, and the publish-lane fix
has to be in place before either package is published. has to be in place before either package is published.
1. `git mv cubes packages/cubes-core/cubes` — preserves per-file history. 1. `git mv cubes packages/nopy-cubes-core/cubes` — preserves per-file history.
2. Add `packages/cubes-core/package.json` per the Phase 1 contract. Version 2. Add `packages/nopy-cubes-core/package.json` per the Phase 1 contract. Version
`1.0.0-alpha0`, tracking the current alpha train. Not private. Its `1.0.0-alpha0`, tracking the current alpha train. Not private. Its
`@bitsquare/nopy-cube` dependency uses `workspace:*` in the repo, which is `@bitsquare/nopy-cubes` dependency uses `workspace:*` in the repo, which is
exactly the case the Phase 4 publish fix has to handle. exactly the case the Phase 4 publish fix has to handle.
Migrating the manifests' `import { cubes } from '@bitsquare/nopy'` to Migrating the manifests' `import { cubes } from '@bitsquare/nopy'` to
`import { Manifest } from '@bitsquare/nopy-cube'` is optional — the re-export `import { Manifest } from '@bitsquare/nopy-cubes'` is optional — the re-export
keeps the old form working — but doing it here is what proves the bundle keeps the old form working — but doing it here is what proves the bundle
resolves without the CLI present at all. resolves without the CLI present at all.
3. Root `.nopyrc.json`: **replace** `"cubeDirs": ["./cubes"]` with 3. Root `.nopyrc.json`: **replace** `"cubeDirs": ["./cubes"]` with
`"cubePackages": ["@bitsquare/cubes-core"]`. Replace, not add — keeping both `"cubePackages": ["@bitsquare/nopy-cubes-core"]`. Replace, not add — keeping both
means every id resolves from two sources and the hard error fires on every means every id resolves from two sources and the hard error fires on every
run. run.
4. Root `package.json`: add `"@bitsquare/cubes-core": "workspace:*"` to 4. Root `package.json`: add `"@bitsquare/nopy-cubes-core": "workspace:*"` to
`devDependencies`, so pnpm symlinks it into the root `node_modules`. This is `devDependencies`, so pnpm symlinks it into the root `node_modules`. This is
what makes the PoC exercise the real pnpm symlink resolution path rather than what makes the PoC exercise the real pnpm symlink resolution path rather than
a plain directory. a plain directory.
5. `packages/nopy/.nopyrc.json` keeps `"cubeDirs": ["./cubes"]` for its fixtures. 5. `packages/nopy/.nopyrc.json` keeps `"cubeDirs": ["./cubes"]` for its fixtures.
Config merges root-first, so running from `packages/nopy` now pulls in Config merges root-first, so running from `packages/nopy` now pulls in
`@bitsquare/cubes-core` *and* the fixtures — which is exactly the collision `@bitsquare/nopy-cubes-core` *and* the fixtures — which is exactly the collision
Phase 0.3 renames away. Phase 0.3 renames away.
6. Workflow changes are limited to the publish-lane fix from Phase 4. 6. Workflow changes are limited to the publish-lane fix from Phase 4.
`publish-snapshot.yml` loops `for dir in packages/*/` and picks both new `publish-snapshot.yml` loops `for dir in packages/*/` and picks both new
packages up automatically; `release.yml` resolves `packages/<pkg>` from the packages up automatically; `release.yml` resolves `packages/<pkg>` from the
tag, so `cubes-core-v1.0.0` and `nopy-cube-v1.0.0` work as-is. Verify on the tag, so `nopy-cubes-core-v1.0.0` and `nopy-cubes-v1.0.0` work as-is. Verify on the
first snapshot run that a package with no `build` script is skipped cleanly by first snapshot run that a package with no `build` script is skipped cleanly by
`pnpm -r run build` (it is) and that publishing is happy with no lifecycle `pnpm -r run build` (it is) and that publishing is happy with no lifecycle
scripts. scripts.
7. No `tsconfig` reference for `cubes-core` — the bundle has no TypeScript. (The 7. No `tsconfig` reference for `nopy-cubes-core` — the bundle has no TypeScript. (The
`nopy-cube` references from Phase 4 are separate.) `nopy-cubes` references from Phase 4 are separate.)
8. Biome already lints `cubes/**/*.mjs` from the root; only the path changes. 8. Biome already lints `cubes/**/*.mjs` from the root; only the path changes.
### What differed from the plan ### What differed from the plan
- **Step 2's optional migration was done.** All 22 manifests now import - **Step 2's optional migration was done.** All 22 manifests now import
`{ Manifest }` from `@bitsquare/nopy-cube`, not `{ cubes }` from `{ Manifest }` from `@bitsquare/nopy-cubes`, not `{ cubes }` from
`@bitsquare/nopy`. Optional for correctness, but it is the only version of the `@bitsquare/nopy`. Optional for correctness, but it is the only version of the
PoC that proves anything: leaving the old import in place would have resolved PoC that proves anything: leaving the old import in place would have resolved
through the CLI that happens to sit in the same tree. through the CLI that happens to sit in the same tree.
- **`uniqid` had to move too.** Two manifests use it (`admin:hostname` bare, - **`uniqid` had to move too.** Two manifests use it (`admin:hostname` bare,
`user:add` via `cubes.uniqid`), so `src/cubes/utils.ts` and its test went to `user:add` via `cubes.uniqid`), so `src/cubes/utils.ts` and its test went to
`nopy-cube` alongside `types.ts`, and `uniqid` joined the authoring barrel. `nopy-cubes` alongside `types.ts`, and `uniqid` joined the authoring barrel.
Otherwise one migrated manifest would still have been importing the CLI. Otherwise one migrated manifest would still have been importing the CLI.
- **`files` needs a log exclusion.** Cubes that have been run leave a gitignored - **`files` needs a log exclusion.** Cubes that have been run leave a gitignored
`pyinfra-debug.log` next to `deploy.py`; gitignore does not filter an npm `pyinfra-debug.log` next to `deploy.py`; gitignore does not filter an npm
tarball. `"files": ["cubes", "!cubes/**/*.log", …]` does. Verified: 22 tarball. `"files": ["cubes", "!cubes/**/*.log", …]` does. Verified: 22
manifests, 22 deploy scripts, 0 logs in the packed artefact. manifests, 22 deploy scripts, 0 logs in the packed artefact.
- **`verify-pack.mjs` picks the bundle up for free** — it walks every non-private - **`verify-pack.mjs` picks the bundle up for free** — it walks every non-private
`packages/*`, so `cubes-core`'s `workspace:*` edge is checked like nopy's. `packages/*`, so `nopy-cubes-core`'s `workspace:*` edge is checked like nopy's.
### Verifying the PoC — done ### Verifying the PoC — done
- **In-workspace:** the built loader, run from the repo root against the new - **In-workspace:** the built loader, run from the repo root against the new
root `.nopyrc.json`, reads 22 cubes with 0 errors and reports root `.nopyrc.json`, reads 22 cubes with 0 errors and reports
`source: { type: 'package', packageName: '@bitsquare/cubes-core', dir: `source: { type: 'package', packageName: '@bitsquare/nopy-cubes-core', dir:
'…/node_modules/@bitsquare/cubes-core/cubes' }` — the pnpm symlink path, not a '…/node_modules/@bitsquare/nopy-cubes-core/cubes' }` — the pnpm symlink path, not a
plain directory. plain directory.
- **Out-of-workspace (the real test):** `pnpm pack` for `nopy-cube`, `nopy` and - **Out-of-workspace (the real test):** `pnpm pack` for `nopy-cubes`, `nopy` and
`cubes-core`, then **`npm install`** of all three tarballs into a throwaway `nopy-cubes-core`, then **`npm install`** of all three tarballs into a throwaway
directory with a `.nopyrc.json` naming only the bundle. npm is the strict test directory with a `.nopyrc.json` naming only the bundle. npm is the strict test
here — it does not understand `workspace:`, so a leaked range fails the install here — it does not understand `workspace:`, so a leaked range fails the install
outright. It installed clean, and the installed outright. It installed clean, and the installed
`@bitsquare/nopy/package.json` carries `"@bitsquare/nopy-cube": `@bitsquare/nopy/package.json` carries `"@bitsquare/nopy-cubes":
"1.0.0-alpha0"`. `nopy install -l session.json -P -D` then resolved "1.0.0-alpha0"`. `nopy install -l session.json -P -D` then resolved
`apt:essentials` and printed a `--chdir` into `apt:essentials` and printed a `--chdir` into
`node_modules/@bitsquare/cubes-core/cubes/apt/essentials`. Since the loader `node_modules/@bitsquare/nopy-cubes-core/cubes/apt/essentials`. Since the loader
aborts on any manifest error and this run did not, all 22 manifests imported aborts on any manifest error and this run did not, all 22 manifests imported
`@bitsquare/nopy-cube` and `zod` successfully from a tree containing no `@bitsquare/nopy-cubes` and `zod` successfully from a tree containing no
workspace links. workspace links.
Note for anyone repeating this: `-P` on its own is interactive, and a replay Note for anyone repeating this: `-P` on its own is interactive, and a replay
@@ -592,15 +592,15 @@ has to be in place before either package is published.
## Phase 6 — documentation — **done** ## Phase 6 — documentation — **done**
- `CLAUDE.md`: the repo table gains two rows (`packages/nopy-cube`, - `CLAUDE.md`: the repo table gains two rows (`packages/nopy-cubes`,
`packages/cubes-core`) and loses the `cubes/` one; "The two packages do not `packages/nopy-cubes-core`) and loses the `cubes/` one; "The two packages do not
depend on each other" is no longer true; the loader section in *nopy depend on each other" is no longer true; the loader section in *nopy
architecture*; and the *Gotcha* paragraph, which the resolve hook retires. architecture*; and the *Gotcha* paragraph, which the resolve hook retires.
- `packages/nopy/docs/CUBE-BUNDLES.md` (new): authoring guide — package shape, - `packages/nopy/docs/CUBE-BUNDLES.md` (new): authoring guide — package shape,
read-only constraint, id collision policy, publishing. read-only constraint, id collision policy, publishing.
- `packages/nopy/docs/API.md` + `README.md`: `cubePackages`. - `packages/nopy/docs/API.md` + `README.md`: `cubePackages`.
- `README.PUBLISH.md`: `nopy-cube-v*` and `cubes-core-v*` as new tag prefixes, - `README.PUBLISH.md`: `nopy-cubes-v*` and `nopy-cubes-core-v*` as new tag prefixes,
plus the ordering constraint — `nopy-cube` releases before anything that plus the ordering constraint — `nopy-cubes` releases before anything that
depends on it. depends on it.
Beyond the list: `CLAUDE.md` also needed the `typecheck` command corrected Beyond the list: `CLAUDE.md` also needed the `typecheck` command corrected
@@ -649,7 +649,7 @@ the error is identical regardless of scan order (Phase 0.2).
`tests/prompts.test.ts``coerceValue` against schemas built by a *different* `tests/prompts.test.ts``coerceValue` against schemas built by a *different*
zod instance, so Phase 0.4 cannot silently regress to `instanceof`. zod instance, so Phase 0.4 cannot silently regress to `instanceof`.
`packages/nopy-cube/` — its own `vitest.config.ts` at the same thresholds. The `packages/nopy-cubes/` — its own `vitest.config.ts` at the same thresholds. The
`Manifest()` / `Manifest.create()` / `Cube.getDefaults()` cases move over from `Manifest()` / `Manifest.create()` / `Cube.getDefaults()` cases move over from
`tests/cubes.factories.test.ts`; what stays behind is whatever tests the `tests/cubes.factories.test.ts`; what stays behind is whatever tests the
re-export surface. re-export surface.
@@ -678,7 +678,7 @@ against a fixture tree, under the existing `test:integration` script.
publish time and only shows up when someone installs the package. Phase 4 publish time and only shows up when someone installs the package. Phase 4
fixes the lane; a `postpack` assertion that no dependency range starts with fixes the lane; a `postpack` assertion that no dependency range starts with
`workspace:` would make it impossible to regress. `workspace:` would make it impossible to regress.
7. **Three packages, three version lines.** `nopy-cube` is the contract, so a 7. **Three packages, three version lines.** `nopy-cubes` is the contract, so a
breaking change there ripples to every published bundle in the wild — which is breaking change there ripples to every published bundle in the wild — which is
the point of versioning it separately, but it means the compatibility question the point of versioning it separately, but it means the compatibility question
from risk 4 gets more pressing, not less. from risk 4 gets more pressing, not less.
+1 -1
View File
@@ -54,7 +54,7 @@
"test:watch": "vitest" "test:watch": "vitest"
}, },
"dependencies": { "dependencies": {
"@bitsquare/nopy-cube": "workspace:*", "@bitsquare/nopy-cubes": "workspace:*",
"@logtape/logtape": "^2.2.4", "@logtape/logtape": "^2.2.4",
"commander": "^15.0.0", "commander": "^15.0.0",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
+1 -1
View File
@@ -3,7 +3,7 @@
* @module cubes/dependencies * @module cubes/dependencies
*/ */
import type { Cube, CubeVariables, HookContext } from '@bitsquare/nopy-cube'; import type { Cube, CubeVariables, HookContext } from '@bitsquare/nopy-cubes';
import { getLogger } from '@logtape/logtape'; import { getLogger } from '@logtape/logtape';
import type { Variables } from '../nopy.common.js'; import type { Variables } from '../nopy.common.js';
import type { NopyConfig } from '../nopy.config.js'; import type { NopyConfig } from '../nopy.config.js';
+2 -2
View File
@@ -17,7 +17,7 @@ export type {
Hook, Hook,
HookContext, HookContext,
LoadResult, LoadResult,
} from '@bitsquare/nopy-cube'; } from '@bitsquare/nopy-cubes';
export { export {
Cube, Cube,
createManifest, createManifest,
@@ -26,7 +26,7 @@ export {
uniqid, uniqid,
zodInner, zodInner,
zodKind, zodKind,
} from '@bitsquare/nopy-cube'; } from '@bitsquare/nopy-cubes';
// Dependencies // Dependencies
export { BuildContext } from './dependencies.js'; export { BuildContext } from './dependencies.js';
// Loader // Loader
+2 -2
View File
@@ -5,7 +5,7 @@
import module from 'node:module'; import module from 'node:module';
import path from 'node:path'; import path from 'node:path';
import { Cube, type CubeSource, type LoadResult, type Manifest } from '@bitsquare/nopy-cube'; import { Cube, type CubeSource, type LoadResult, type Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod'; import { z } from 'zod';
import { fs } from 'zx'; import { fs } from 'zx';
import { loadConfig } from '../nopy.config.js'; import { loadConfig } from '../nopy.config.js';
@@ -15,7 +15,7 @@ let hookRegistered = false;
/** /**
* Installs the fallback resolver that lets a manifest in a bare directory * Installs the fallback resolver that lets a manifest in a bare directory
* import `@bitsquare/nopy-cube` or `zod` — see `resolve-hook.mjs`. * import `@bitsquare/nopy-cubes` or `zod` — see `resolve-hook.mjs`.
* *
* `module.register()` is process-global and cannot be undone, so this runs once * `module.register()` is process-global and cannot be undone, so this runs once
* and only when cubes are about to be imported. Registration failing is not * and only when cubes are about to be imported. Registration failing is not
+2 -2
View File
@@ -5,7 +5,7 @@
* A manifest is loaded with `import(manifestPath)`, so its imports resolve from * A manifest is loaded with `import(manifestPath)`, so its imports resolve from
* its own directory. A cube sitting in an arbitrary `cubeDirs` entry — no * its own directory. A cube sitting in an arbitrary `cubeDirs` entry — no
* package.json above it, no node_modules beside it — therefore cannot import * package.json above it, no node_modules beside it — therefore cannot import
* `@bitsquare/nopy-cube` or `zod` at all, and the run dies on * `@bitsquare/nopy-cubes` or `zod` at all, and the run dies on
* ERR_MODULE_NOT_FOUND before a single deploy is built. * ERR_MODULE_NOT_FOUND before a single deploy is built.
* *
* A published cube bundle never reaches this: it declares its own dependencies * A published cube bundle never reaches this: it declares its own dependencies
@@ -25,7 +25,7 @@ import { pathToFileURL } from 'node:url';
* be expected to install for itself. Anything else stays a hard failure — a * be expected to install for itself. Anything else stays a hard failure — a
* cube that wants a library should depend on it. * cube that wants a library should depend on it.
*/ */
const FALLBACK_ROOTS = ['@bitsquare/nopy-cube', '@bitsquare/nopy', 'zod']; const FALLBACK_ROOTS = ['@bitsquare/nopy-cubes', '@bitsquare/nopy', 'zod'];
/** @type {NodeRequire | undefined} */ /** @type {NodeRequire | undefined} */
let fallbackRequire; let fallbackRequire;
+1 -1
View File
@@ -3,7 +3,7 @@
* @module nopy.executor * @module nopy.executor
*/ */
import type { DependencySpec } from '@bitsquare/nopy-cube'; import type { DependencySpec } from '@bitsquare/nopy-cubes';
import { getLogger } from '@logtape/logtape'; import { getLogger } from '@logtape/logtape';
import { execa } from 'execa'; import { execa } from 'execa';
import { MASK } from './nopy.common.js'; import { MASK } from './nopy.common.js';

Some files were not shown because too many files have changed in this diff Show More