streamline package naming

This commit is contained in:
Benjamin Diedrichsen
2026-07-29 13:07:34 +02:00
parent 1ba1c2a32a
commit 7e703c93b1
100 changed files with 141 additions and 139 deletions
+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,10 +1,10 @@
{ {
"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": [
"nopy", "nopy",
"nopy-cubes", "nopy-cubess",
"pyinfra", "pyinfra",
"deployment", "deployment",
"infrastructure" "infrastructure"
@@ -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';
@@ -2,7 +2,7 @@
* Edge cases for BuildContext: unknown cubes, session replay and auth flags. * Edge cases for BuildContext: unknown cubes, session replay and auth flags.
*/ */
import { type AnyObjectSchema, Cube, Manifest } from '@bitsquare/nopy-cube'; import { type AnyObjectSchema, Cube, Manifest } from '@bitsquare/nopy-cubes';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { z } from 'zod'; import { z } from 'zod';
import { BuildContext } from '../src/cubes/dependencies.js'; import { BuildContext } from '../src/cubes/dependencies.js';
@@ -2,7 +2,7 @@
* Tests for cubes/dependencies module (BuildContext) * Tests for cubes/dependencies module (BuildContext)
*/ */
import { Cube, Manifest } from '@bitsquare/nopy-cube'; import { Cube, Manifest } from '@bitsquare/nopy-cubes';
import { describe, expect, it, vi } from 'vitest'; import { describe, expect, it, vi } from 'vitest';
import { z } from 'zod'; import { z } from 'zod';
import { BuildContext } from '../src/cubes/dependencies.js'; import { BuildContext } from '../src/cubes/dependencies.js';
+1 -1
View File
@@ -2,7 +2,7 @@
* Tests for cube hooks using BuildContext * Tests for cube hooks using BuildContext
*/ */
import { Cube, Manifest } from '@bitsquare/nopy-cube'; import { Cube, Manifest } from '@bitsquare/nopy-cubes';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { z } from 'zod'; import { z } from 'zod';
import { BuildContext } from '../src/cubes/dependencies.js'; import { BuildContext } from '../src/cubes/dependencies.js';
+1 -1
View File
@@ -37,7 +37,7 @@ vi.mock('enquirer', () => ({
}, },
})); }));
import { Cube, Manifest } from '@bitsquare/nopy-cube'; import { Cube, Manifest } from '@bitsquare/nopy-cubes';
import { Variables } from '../src/nopy.common.js'; import { Variables } from '../src/nopy.common.js';
import { import {
AuthSelection, AuthSelection,
+1 -1
View File
@@ -11,5 +11,5 @@
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["coverage", "node_modules", "dist"], "exclude": ["coverage", "node_modules", "dist"],
"references": [{ "path": "../nopy-cube" }] "references": [{ "path": "../nopy-cubes" }]
} }
+5 -3
View File
@@ -4,10 +4,12 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
// The workspace link points at nopy-cube's `dist`, which only exists after // The workspace link points at nopy-cubes's `dist`, which only exists after
// a build. Tests read its source instead, so the gate does not depend on // a build. Tests read its source instead, so the gate does not depend on
// build ordering and never runs against a stale artifact. // build ordering and never runs against a stale artifact.
'@bitsquare/nopy-cube': fileURLToPath(new URL('../nopy-cube/src/index.ts', import.meta.url)), '@bitsquare/nopy-cubes': fileURLToPath(
new URL('../nopy-cubes/src/index.ts', import.meta.url)
),
}, },
}, },
test: { test: {
@@ -22,7 +24,7 @@ export default defineConfig({
exclude: [ exclude: [
'src/**/*.test.ts', 'src/**/*.test.ts',
// Reached through the resolve.alias above; it has its own gate. // Reached through the resolve.alias above; it has its own gate.
'**/nopy-cube/**', '**/nopy-cubes/**',
// Pure re-export barrels: no logic to cover. // Pure re-export barrels: no logic to cover.
'src/index.ts', 'src/index.ts',
'src/cubes/index.ts', 'src/cubes/index.ts',