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
# 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.
for dir in $dirs; do
base=$(node -p "require('./${dir}/package.json').version")
+3 -3
View File
@@ -1,13 +1,13 @@
# Tag-driven release of a single package.
#
# 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
#
# 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.
#
# 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
# to publish otherwise.
# 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
# registry yet, the release installs to a broken tree — and npmjs
# 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
# to read, which this step does not have yet.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"hosts": [],
"cubeDirs": [],
"cubePackages": ["@bitsquare/cubes-core"],
"cubePackages": ["@bitsquare/nopy-cubes-core"],
"env": {},
"log": {
"verbosity": "info",
+19 -19
View File
@@ -7,18 +7,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
A pnpm workspace holding two independently published CLIs, the authoring package
their deployment units are written against, and one bundle of those units:
| Path | Package | Binary | Role |
| --------------------- | ---------------------- | -------- | -------------------------------------------------------- |
| `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/nopy-cube` | `@bitsquare/nopy-cube` | — | the authoring surface a `manifest.mjs` imports |
| `packages/cubes-core` | `@bitsquare/cubes-core`| — | the core cube bundle (22 cubes), no TypeScript |
| Path | Package | Binary | Role |
| -------------------------- | ----------------------------- | -------- | -------------------------------------------------------- |
| `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/nopy-cubes` | `@bitsquare/nopy-cubes` | — | the authoring surface a `manifest.mjs` imports |
| `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
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*.
`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
`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;
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
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
it nopy's numbers absorb another package's files. `cubes-core` has no tests of its
stale artefact. The same config excludes `**/nopy-cubes/**` from coverage — without
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.
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
ignored by the loader but reachable from the script, which runs with the cube
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
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,
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
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.
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`
retires that: `loadCubes()` registers a `module.register()` resolve hook that
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
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
@@ -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
`--registry` would send every transitive dependency to a registry that has never
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
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.
Note `pnpm pack` has no `--ignore-scripts` flag, so `prepack` does rebuild —
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;
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
@@ -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
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
`@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`
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;
`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
> 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 —
> `user:add` lost its `PUBKEY` default (it was a specific personal key), and
> `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/keyman` | `@bitsquare/keyman` | `keyman` | CLI |
| `packages/nopy-cube` | `@bitsquare/nopy-cube` | — | library (cube authoring) |
| `packages/cubes-core` | `@bitsquare/cubes-core` | — | cube bundle (no build) |
| `packages/nopy-cubes` | `@bitsquare/nopy-cubes` | — | library (cube authoring) |
| `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
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
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`
next to its `deploy.py`, and `.gitignore` does not filter an npm tarball.
`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
`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
back to:
@@ -55,7 +55,7 @@ back to:
through verbatim and the install fails with `EUNSUPPORTEDPROTOCOL`. `pnpm
pack` and `pnpm publish` substitute the concrete version at pack time. Both
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
dependency order — note that plain alphabetical `packages/*/` gets this
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
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
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.
### `release.yml`
@@ -144,7 +144,7 @@ the whole gate.
*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
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
npmjs-only: it runs before any credentials are written, and npmjs is the registry
where the mistake is permanent.
@@ -253,26 +253,26 @@ the same way:
```sh
git tag nopy-v1.2.0
git tag keyman-v1.2.0
git tag nopy-cube-v1.2.0
git tag cubes-core-v1.2.0
git tag nopy-cubes-v1.2.0
git tag nopy-cubes-core-v1.2.0
```
### Ordering when more than one package changed
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:
```
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
you the `nopy-cube` 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
you the `nopy-cubes` version it wanted is not on npmjs. That is the guard working;
release `nopy-cubes`, then re-tag. `node scripts/publish-order.mjs` prints the
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
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
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:
```sh
pnpm add -D @bitsquare/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-core # then name it in .nopyrc.json cubePackages
pnpm add -D @bitsquare/nopy-cubes zod # authoring your own manifests
```
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,
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;
- prints the three resolved versions, so you can see which commit you are on;
- 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.
```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/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
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
```
@@ -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. |
| 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. |
| `... 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. |
## Recovering from a bad publish
+1 -1
View File
@@ -28,7 +28,7 @@
},
"devDependencies": {
"@biomejs/biome": "^2.5.5",
"@bitsquare/cubes-core": "workspace:*",
"@bitsquare/nopy-cubes-core": "workspace:*",
"@logtape/logtape": "^2.2.4",
"@types/node": "^26.1.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):
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
```sh
pnpm add -D @bitsquare/cubes-core
pnpm add -D @bitsquare/nopy-cubes-core
```
Then name it in `.nopyrc.json`:
@@ -14,7 +14,7 @@ Then name it in `.nopyrc.json`:
```json
{
"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({
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';
/**
@@ -12,7 +12,7 @@ Configures system keyboard layout permanently by updating `/etc/default/keyboard
## Usage
```javascript
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
export default Manifest({
name: 'My Host Setup',
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
export default Manifest({
id: 'armor:fail2ban',
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
// [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';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
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';
export default Manifest({
@@ -1,4 +1,4 @@
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
// [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",
"description": "The core nopy cube bundle: apt, users, ssh, networking, services and runtimes.",
"keywords": [
@@ -15,9 +15,9 @@
"repository": {
"type": "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": {
"url": "https://gitea.bitsquare.dev/BitSquare/ansiblings/issues"
},
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
"@bitsquare/nopy-cube": "workspace:*",
"@bitsquare/nopy-cubes": "workspace:*",
"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)
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
```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
@@ -22,7 +22,7 @@ the `Manifest` factory should all see the same copy.
```js
// cubes/net/tailscale/manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -1,5 +1,5 @@
{
"name": "@bitsquare/nopy-cube",
"name": "@bitsquare/nopy-cubes",
"version": "0.5.0",
"description": "Authoring types for nopy cubes: the Manifest factory and the Cube contract.",
"keywords": [
@@ -14,9 +14,9 @@
"repository": {
"type": "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": {
"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
* 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"],
"cubeDirs": ["./cubes", "../shared-cubes"],
"cubePackages": ["@bitsquare/cubes-core"],
"cubePackages": ["@bitsquare/nopy-cubes-core"],
"env": {
"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:
```sh
pnpm add -D @bitsquare/cubes-core
pnpm add -D @bitsquare/nopy-cubes-core
```
```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.
@@ -341,11 +341,11 @@ The cubes live in a separate bundle, installed into whichever project describes
your infrastructure and named in its `.nopyrc.json`:
```bash
pnpm add -D @bitsquare/cubes-core
pnpm add -D @bitsquare/nopy-cubes-core
```
```json
{ "hosts": ["your-host"], "cubePackages": ["@bitsquare/cubes-core"] }
{ "hosts": ["your-host"], "cubePackages": ["@bitsquare/nopy-cubes-core"] }
```
#### Channels
+14 -14
View File
@@ -1,7 +1,7 @@
# Nopy API Reference
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
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
[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
- [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)
- [Cubes Module](#cubes-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 |
| --- | --- | --- |
| `@bitsquare/nopy-cube` | `Manifest`, `Cube`, `Hook`, `uniqid`, the zod helpers | zod (peer) |
| `@bitsquare/nopy` | the CLI, the loader, config, sessions, execution | `@bitsquare/nopy-cube` |
| `@bitsquare/nopy-cubes` | `Manifest`, `Cube`, `Hook`, `uniqid`, the zod helpers | zod (peer) |
| `@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
depend on it without pulling the whole tool into its dependency graph.
```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
```
@@ -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)`
@@ -65,7 +65,7 @@ Factory for a cube manifest. `name` is the only required option; everything else
is filled in.
```javascript
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -85,7 +85,7 @@ Defaults applied by the factory: `id: ''`, `schema: z.object({})`,
`createManifest` and `manifest` are exported as identical aliases.
`ManifestFactory` is a third alias, marked `@deprecated` — and it is not
re-exported through `@bitsquare/nopy`, so it is only reachable from
`@bitsquare/nopy-cube` directly.
`@bitsquare/nopy-cubes` directly.
#### `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
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
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.
@@ -811,7 +811,7 @@ type ResolutionConfig = { [K in keyof NopyConfig]?: ResolutionStrategy };
### `CubePackageRef`
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
interface CubePackageRef {
@@ -848,7 +848,7 @@ objects deep-merge, primitives are replaced. `override` replaces outright.
```json
{
"hosts": ["local-host"],
"cubePackages": ["@bitsquare/cubes-core"],
"cubePackages": ["@bitsquare/nopy-cubes-core"],
"resolution": { "hosts": "override" }
}
```
@@ -1111,7 +1111,7 @@ working directory. The prefixed forms `<name>.manifest.mjs` and
```javascript
// manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
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:
```json
{ "cubePackages": ["@bitsquare/cubes-core"] }
{ "cubePackages": ["@bitsquare/nopy-cubes-core"] }
```
The rest of this document is for writing one.
@@ -39,7 +39,7 @@ and imports each `manifest.mjs` directly.
└── 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
special-cased.
@@ -53,7 +53,7 @@ special-cased.
"files": ["cubes", "!cubes/**/*.log", "README.md", "LICENSE"],
"publishConfig": { "access": "public" },
"dependencies": {
"@bitsquare/nopy-cube": "^1.0.0",
"@bitsquare/nopy-cubes": "^1.0.0",
"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
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
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
@@ -101,7 +101,7 @@ directory as its working directory.
```javascript
// cubes/nginx/manifest.mjs
import { Manifest } from '@bitsquare/nopy-cube';
import { Manifest } from '@bitsquare/nopy-cubes';
import { z } from 'zod';
export default Manifest({
@@ -127,7 +127,7 @@ SERVER_NAME = host.data.SERVER_NAME
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
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
@@ -214,10 +214,10 @@ the cube scan itself.
**How a manifest finds its imports.** Ordinary Node resolution, from the
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
`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
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.
@@ -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. |
| `'…' 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. |
| `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. |
| 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.**
The one thing still unproven is the publish lane against a real registry — see
*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
`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. |
| 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`. |
| Migrate in-repo `cubes/` | Yes — `packages/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*. |
| Migrate in-repo `cubes/` | Yes — `packages/nopy-cubes-core`, as the proof of concept. |
| 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
@@ -142,9 +142,9 @@ A cube bundle is an npm package with a `nopy` field:
"version": "1.0.0",
"type": "module",
"files": ["cubes", "README.md", "LICENSE"],
"keywords": ["nopy", "nopy-cubes", "pyinfra"],
"keywords": ["nopy", "nopy-cubess", "pyinfra"],
"dependencies": {
"@bitsquare/nopy-cube": "^1.0.0",
"@bitsquare/nopy-cubes": "^1.0.0",
"zod": "^4.4.3"
},
"publishConfig": { "access": "public" }
@@ -164,8 +164,8 @@ Rules:
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
load-bearing: a manifest imports `Manifest` from `@bitsquare/nopy-cubes` and `z`
from `zod`. `@bitsquare/nopy-cubes` peer-depends on zod, so the bundle's copy is
the one everybody uses — see Phase 4.
- The package needs no `exports` entry for this to work — resolution reads
`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:
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
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
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
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
`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
`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
@@ -338,7 +338,7 @@ moves independently of the CLI's.
```json
{
"name": "@bitsquare/nopy-cube",
"name": "@bitsquare/nopy-cubes",
"version": "1.0.0-alpha0",
"type": "module",
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
@@ -355,7 +355,7 @@ is still the right default.
### 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
already coverage-excluded barrels — so `import { cubes } from '@bitsquare/nopy'`
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:
- `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.
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
@@ -377,12 +377,12 @@ Repo plumbing it took:
`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`,
which is gitignored.
- `packages/nopy/package.json`: `"@bitsquare/nopy-cube": "workspace:*"`.
- `packages/nopy/vitest.config.ts`: a `resolve.alias` for `@bitsquare/nopy-cube`
pointing at `../nopy-cube/src/index.ts`. Without it the workspace link
- `packages/nopy/package.json`: `"@bitsquare/nopy-cubes": "workspace:*"`.
- `packages/nopy/vitest.config.ts`: a `resolve.alias` for `@bitsquare/nopy-cubes`
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
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.
- A `vitest.config.ts` for the new package with the same thresholds. It sits at
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
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:*`.
But **both workflows publish with `npm publish`**, and npm does not understand
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
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.
Pick one before publishing anything:
@@ -408,13 +408,13 @@ Pick one before publishing anything:
workflows and pulls in pnpm's own lifecycle behaviour.
- **Rewrite the range with `npm pkg set` before publishing**, extending the
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
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
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
works.
@@ -431,7 +431,7 @@ locally:
in both workflows. Turns "npm would have shipped a broken manifest" from an
install-time surprise into a red run.
- **`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.
- **`scripts/linked-deps.mjs`** — lists a package's workspace links as
`<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
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.
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.
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.
**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
`dist` entry.
- 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
(`@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
`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
`@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.
1. `git mv cubes packages/cubes-core/cubes` — preserves per-file history.
2. Add `packages/cubes-core/package.json` per the Phase 1 contract. Version
1. `git mv cubes packages/nopy-cubes-core/cubes` — preserves per-file history.
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
`@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.
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
resolves without the CLI present at all.
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
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
what makes the PoC exercise the real pnpm symlink resolution path rather than
a plain directory.
5. `packages/nopy/.nopyrc.json` keeps `"cubeDirs": ["./cubes"]` for its fixtures.
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.
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
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
`pnpm -r run build` (it is) and that publishing is happy with no lifecycle
scripts.
7. No `tsconfig` reference for `cubes-core` — the bundle has no TypeScript. (The
`nopy-cube` references from Phase 4 are separate.)
7. No `tsconfig` reference for `nopy-cubes-core` — the bundle has no TypeScript. (The
`nopy-cubes` references from Phase 4 are separate.)
8. Biome already lints `cubes/**/*.mjs` from the root; only the path changes.
### What differed from the plan
- **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
PoC that proves anything: leaving the old import in place would have resolved
through the CLI that happens to sit in the same tree.
- **`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
`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.
- **`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
tarball. `"files": ["cubes", "!cubes/**/*.log", …]` does. Verified: 22
manifests, 22 deploy scripts, 0 logs in the packed artefact.
- **`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
- **In-workspace:** the built loader, run from the repo root against the new
root `.nopyrc.json`, reads 22 cubes with 0 errors and reports
`source: { type: 'package', packageName: '@bitsquare/cubes-core', dir:
'…/node_modules/@bitsquare/cubes-core/cubes' }` — the pnpm symlink path, not a
`source: { type: 'package', packageName: '@bitsquare/nopy-cubes-core', dir:
'…/node_modules/@bitsquare/nopy-cubes-core/cubes' }` — the pnpm symlink path, not a
plain directory.
- **Out-of-workspace (the real test):** `pnpm pack` for `nopy-cube`, `nopy` and
`cubes-core`, then **`npm install`** of all three tarballs into a throwaway
- **Out-of-workspace (the real test):** `pnpm pack` for `nopy-cubes`, `nopy` and
`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
here — it does not understand `workspace:`, so a leaked range fails the install
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
`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
`@bitsquare/nopy-cube` and `zod` successfully from a tree containing no
`@bitsquare/nopy-cubes` and `zod` successfully from a tree containing no
workspace links.
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**
- `CLAUDE.md`: the repo table gains two rows (`packages/nopy-cube`,
`packages/cubes-core`) and loses the `cubes/` one; "The two packages do not
- `CLAUDE.md`: the repo table gains two rows (`packages/nopy-cubes`,
`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
architecture*; and the *Gotcha* paragraph, which the resolve hook retires.
- `packages/nopy/docs/CUBE-BUNDLES.md` (new): authoring guide — package shape,
read-only constraint, id collision policy, publishing.
- `packages/nopy/docs/API.md` + `README.md`: `cubePackages`.
- `README.PUBLISH.md`: `nopy-cube-v*` and `cubes-core-v*` as new tag prefixes,
plus the ordering constraint — `nopy-cube` releases before anything that
- `README.PUBLISH.md`: `nopy-cubes-v*` and `nopy-cubes-core-v*` as new tag prefixes,
plus the ordering constraint — `nopy-cubes` releases before anything that
depends on it.
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*
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
`tests/cubes.factories.test.ts`; what stays behind is whatever tests the
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
fixes the lane; a `postpack` assertion that no dependency range starts with
`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
the point of versioning it separately, but it means the compatibility question
from risk 4 gets more pressing, not less.
+1 -1
View File
@@ -54,7 +54,7 @@
"test:watch": "vitest"
},
"dependencies": {
"@bitsquare/nopy-cube": "workspace:*",
"@bitsquare/nopy-cubes": "workspace:*",
"@logtape/logtape": "^2.2.4",
"commander": "^15.0.0",
"enquirer": "^2.4.1",
+1 -1
View File
@@ -3,7 +3,7 @@
* @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 type { Variables } from '../nopy.common.js';
import type { NopyConfig } from '../nopy.config.js';
+2 -2
View File
@@ -17,7 +17,7 @@ export type {
Hook,
HookContext,
LoadResult,
} from '@bitsquare/nopy-cube';
} from '@bitsquare/nopy-cubes';
export {
Cube,
createManifest,
@@ -26,7 +26,7 @@ export {
uniqid,
zodInner,
zodKind,
} from '@bitsquare/nopy-cube';
} from '@bitsquare/nopy-cubes';
// Dependencies
export { BuildContext } from './dependencies.js';
// Loader
+2 -2
View File
@@ -5,7 +5,7 @@
import module from 'node:module';
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 { fs } from 'zx';
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
* 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
* 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
* its own directory. A cube sitting in an arbitrary `cubeDirs` entry — no
* 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.
*
* 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
* 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} */
let fallbackRequire;
+1 -1
View File
@@ -3,7 +3,7 @@
* @module nopy.executor
*/
import type { DependencySpec } from '@bitsquare/nopy-cube';
import type { DependencySpec } from '@bitsquare/nopy-cubes';
import { getLogger } from '@logtape/logtape';
import { execa } from 'execa';
import { MASK } from './nopy.common.js';

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