Files
ansiblings/packages/nopy/docs/API.md
T
Benjamin Diedrichsen 6ecb2c366f
Publish snapshot / snapshot (push) Successful in 1m2s
[refactor] moving cubes into own package"
[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
2026-07-28 12:18:10 +02:00

14 KiB

Nopy API Reference

This document describes the public API for the nopy package.


Table of Contents


Main Module

nopy(options?)

Main entry point for nopy deployments.

import { nopy } from '@bitsquare/nopy';

const result = await nopy({
  useDefaults: false,
  dryRun: true,
});

Parameters:

Name Type Default Description
useDefaults boolean false Skip variable prompts, use defaults
useAuthKey boolean false Force SSH key authentication
saveSession string - Path to save session file
loadSession string - Path to load session for replay
dryRun boolean false Show execution plan without running
continueOnError boolean false Continue after failures
jsonOutput boolean false Output results as JSON

Returns: Promise<NopyResult | undefined>

interface NopyResult {
  success: boolean;
  results: ExecutionResult[];
  summary: {
    total: number;
    successful: number;
    failed: number;
    totalDuration: number;
  };
}

Cubes Module

The cubes module provides types and functions for working with deployment units.

The authoring half of it — Manifest, Cube, Hook, uniqid and the rest — actually lives in @bitsquare/nopy-cube, a package with no CLI and no dependency other than zod. @bitsquare/nopy re-exports all of it, so both of these work:

import { Manifest } from '@bitsquare/nopy-cube';  // in a manifest.mjs — prefer this
import { cubes } from '@bitsquare/nopy';          // cubes.Manifest — still supported

Import from nopy-cube in a cube bundle you intend to publish: it lets the bundle depend on the authoring types without pulling the whole CLI in as a dependency. See CUBE-BUNDLES.md.

Types

Cube<Schema>

A fully loaded cube with filesystem location.

interface Cube<Schema extends z.AnyZodObject = z.AnyZodObject> {
  key: string;           // Unique identifier
  name: string;          // Human-readable name
  dir: string;           // Absolute path to cube directory
  source: CubeSource;    // Where it was discovered
  dependencies: string[];
  schema: Schema;
  defaults: () => z.infer<Schema>;
  before: Hook<Schema>[];
  after: Hook<Schema>[];
}

CubeSource

Where a cube came from. Carried so that a duplicate-id error can name the origin of each claimant, which is the difference between a usable error message and a puzzle when the collision is between a local tree and an installed bundle.

type CubeSource =
  | { type: 'dir'; dir: string }
  | { type: 'package'; packageName: string; dir: string };

Manifest<Schema>

Cube manifest (used in manifest.mjs files).

interface Manifest<Schema extends z.AnyZodObject = z.AnyZodObject> {
  name: string;
  key: string;
  dependencies: string[];
  schema: Schema;
  defaults: () => z.infer<Schema>;
  before: Hook<Schema>[];
  after: Hook<Schema>[];
}

Hook<Schema>

Hook function for before/after cube execution. See Cube Hooks for more details.

type Hook<Schema extends z.AnyZodObject> = (
  ctx: HookContext,
  params: z.infer<Schema>
) => void | Promise<void>;

interface HookContext {
  /**
   * Schedules another cube for execution.
   * @param key - The unique identifier or path of the cube.
   * @param params - Variables to pass to the cube.
   */
  exec: (key: string, params: CubeVariables) => Promise<void> | void;
}

Functions

loadCubes()

Loads all cubes from discovered cube directories — cubeDirs, the directories declared by every package in cubePackages, and any ancestor directory holding a .npcubes marker.

const { cubes, errors } = await loadCubes();

Returns: Promise<LoadResult>

interface LoadResult {
  cubes: Record<string, Cube>;
  errors: string[];
}

errors is non-empty for a duplicate id, a manifest that fails to load, a package in cubePackages that is not installed or declares no cubes, and a nopy.cubes entry that is missing or points outside its package. Any of them aborts the run — none is a silent skip.

resolveCubePackages(refs)

Resolves CubePackageRef[] to installed packages and their cube directories. Called by loadCubes(); exported because the resolution failures are worth testing on their own.

const { packages, errors } = resolveCubePackages(config.cubePackages);

interface CubePackage {
  name: string;    // the name it was requested under
  root: string;    // absolute path to the package root
  dirs: string[];  // absolute paths from its `nopy.cubes` field
}

resolveDependencies(cubes, selectedCubeNames)

Resolves all transitive dependencies for selected cubes.

const order = resolveDependencies(cubes, ['apt-all']);
// Returns: ['apt:essentials', 'apt-more', 'apt-all']

Parameters:

Name Type Description
cubes Record<string, Cube> Map of all available cubes
selectedCubeNames string[] Cubes to resolve

Returns: string[] - Cube names in execution order

Throws: Error if cube not found or circular dependency detected

cubes.Manifest(options)

Factory function for creating cube manifests.

import { cubes } from '@bitsquare/nopy';

export default cubes.Manifest({
  name: 'My Cube',
  dependencies: () => [['apt:essentials']],
  schema: z.object({
    VERSION: z.string().default('1.0'),
  }),
});

createManifest and manifest are exported as equivalent aliases; cubes.Manifest is the documented form.

uniqid(length?)

Generates a random alphanumeric string.

const id = uniqid();     // 'Kx7Pm'
const long = uniqid(10); // 'Kx7PmQr2Yw'

Executor Module

Handles pyinfra command execution.

Types

DeployCall

A deployment command ready for execution.

interface DeployCall {
  cube: string;
  host: string;
  cwd: string;
  command: string[];
  env: Record<string, unknown>;
  dependencies: string[];
}

ExecutionResult

Result of executing a deployment command.

interface ExecutionResult {
  cube: string;
  host: string;
  success: boolean;
  duration: number;
  stdout?: string;
  stderr?: string;
  error?: Error;
}

ExecutionOptions

Options for deployment execution.

interface ExecutionOptions {
  continueOnError?: boolean;
  dryRun?: boolean;
  onProgress?: (result: ExecutionResult, completed: number, total: number) => void;
  onStart?: (cube: string, host: string) => void;
}

Functions

executeDeployCalls(calls, options?)

Executes an array of deployment calls sequentially, in the order they were built.

const results = await executeDeployCalls(calls, {
  continueOnError: false,
  onProgress: (result, completed, total) => {
    console.log(`${completed}/${total}`);
  },
});

outputExecutionPlan(calls, asJson?)

Outputs the execution plan without running.

outputExecutionPlan(deployCalls);       // Text output
outputExecutionPlan(deployCalls, true); // JSON output

summarizeResults(results)

Generates a summary of execution results.

const summary = summarizeResults(results);
// {
//   total: 5,
//   successful: 4,
//   failed: 1,
//   totalDuration: 12345,
//   failures: [{ cube: 'docker', ... }]
// }

Builder Module

Constructs deployment commands.

buildDeployCalls(cubeNames, hosts, context)

Builds deployment calls for all cubes and hosts.

const result = await buildDeployCalls(
  ['apt:essentials', 'apt-more'],
  ['@docker/test'],
  {
    cubes,
    session,
    config,
    authMethod: 'ssh-key',
    useDefaults: true,
    isSessionReplay: false,
  }
);

Returns: Promise<BuildResult>

interface BuildResult {
  deployCalls: DeployCall[];
  cubeSessions: CubeSession[];
  sessionEnv: Record<string, unknown>;
}

Workflow Module

Manages interactive and replay workflows.

runWorkflow(sessionPath, cubes, config, options?)

Runs the appropriate workflow based on options.

const result = await runWorkflow(
  undefined,  // null for interactive, path for replay
  cubes,
  config,
  { useDefaults: false }
);

Returns: Promise<WorkflowResult>

interface WorkflowResult {
  session: NopySession;
  cubesWithDependencies: string[];
  authMethod: string;
  username?: string;
  password?: string;
  isReplay: boolean;
}

runInteractiveWorkflow(cubes, config, options?)

Runs the interactive cube selection workflow.

runReplayWorkflow(sessionPath, cubes, config)

Runs a replay from a saved session file.


Session Module

Manages session save/load operations.

Types

NopySession

Complete session configuration.

interface NopySession {
  name?: string;
  cubes: CubeSession[];
  hosts?: string[];
  auth: AuthSession;
  env?: SessionVariables;
}

CubeSession

Configuration for a single cube.

interface CubeSession {
  key: string;
  variables: SessionVariables;
}

AuthSession

Authentication configuration.

interface AuthSession {
  method: 'ssh-key' | 'password' | 'ssh';
  username?: string;
}

Functions

saveSession(session, filePath)

Saves a session to a JSON file.

saveSession(session, './my-deployment.nopysession.json');

loadSession(filePath)

Loads a session from a JSON or MJS file.

const session = await loadSession('./deployment.json');
const session = await loadSession('./deployment.mjs');

createSession(params)

Creates a session object from runtime data.

const session = createSession({
  cubes: [{ key: 'apt:essentials', variables: {} }],
  hosts: ['localhost'],
  auth: { method: 'ssh-key' },
});

listSessions(dirPath?)

Lists all session files in a directory.

const sessions = listSessions('./sessions');
// ['./sessions/deploy.session.json', './sessions/test.session.mjs']

Config Module

Manages nopy configuration.

Types

NopyConfig

Configuration file structure.

interface NopyConfig {
  hosts: string[];
  cubeDirs: string[];
  cubePackages: CubePackageRef[];
  env: EnvConfig;
  log?: LogConfig;
}

CubePackageRef

A package named in cubePackages, paired with where it was named. In the config file an entry is just a string ("@bitsquare/cubes-core"); loadConfig() normalises it.

interface CubePackageRef {
  /** The package name, as written in the config. */
  spec: string;
  /** Directory of the config file that named it — resolution starts here. */
  from: string;
}

from is what makes a package named in a parent config resolve against that config's node_modules, not the working directory's. It is the same problem PATH_PROPERTIES solves for relative cubeDirs.

LogConfig

Logging configuration.

interface LogConfig {
  verbosity?: 'silent' | 'info' | 'verbose' | 'trace';
  debug?: boolean;
}

Functions

loadConfig()

Loads configuration from .nopyrc.json.

const config = loadConfig();

Search order:

  1. ./nopyrc.json (local)
  2. ~/.nopyrc.json (home)

saveConfig(data, local?)

Saves configuration to a file.

saveConfig({ hosts: ['server.local'] });       // Local
saveConfig({ hosts: ['server.local'] }, false); // Home

logConfigToFlags(logConfig?)

Converts log config to pyinfra flags.

logConfigToFlags({ verbosity: 'verbose', debug: true });
// ['-vv', '--debug']

Prompts Module

Interactive prompts for user input.

CubeSelection(cubes)

Prompts user to select cubes to execute.

const { selectedCubes } = await CubeSelection(cubes);

HostSelection(hosts)

Prompts user to select a target host.

const host = await HostSelection(['server1', 'server2']);

AuthSelection(useAuthKey?)

Prompts user to select authentication method.

const { authMethod, username, password } = await AuthSelection();

VariableAssignment(cube, env)

Prompts user to customize cube variables.

const vars = await VariableAssignment(cube, { existing: 'value' });

PasswordSelection(username)

Prompts for password input.

const password = await PasswordSelection('admin');

CLI Usage

# Interactive deployment
nopy install

# With defaults (no prompts)
nopy install -D

# SSH key auth
nopy install -K

# Save session
nopy install -s ./my-session.json

# Replay session
nopy install -l ./my-session.json

# Dry run
nopy install -n

# JSON output
nopy install -j

# Continue on error
nopy install -c

Creating a Cube

File Structure

A cube is a directory containing both a manifest.mjs and a deploy.py:

cubes/
└── my-cube/
    ├── manifest.mjs
    └── deploy.py

Cube directories may be nested for grouping (cubes/apt/install/), and any extra files alongside the pair are available to the deploy script via relative paths.

The prefixed forms <cube-name>.manifest.mjs and <cube-name>.deploy.py are still recognized for backwards compatibility.

Manifest Example

// manifest.mjs
import { cubes } from '@bitsquare/nopy';
import { z } from 'zod';

export default cubes.Manifest({
  name: 'My Cube',
  dependencies: () => [['apt:essentials']],
  schema: z.object({
    VERSION: z.string().default('1.0').describe('Version to install'),
    ENABLE_FEATURE: z.boolean().default(false),
  }),
  before: [
    (ctx, params) => {
      console.log('Before my-cube');
    },
  ],
  after: [
    (ctx, params) => {
      console.log('After my-cube');
    },
  ],
});

Deploy Script Example

# deploy.py
from pyinfra import host
from pyinfra.operations import apt, server

VERSION = host.data.get('VERSION', '1.0')
ENABLE_FEATURE = host.data.get('ENABLE_FEATURE', False)

apt.packages(
    name='Install my-package',
    packages=[f'my-package={VERSION}'],
    update=True,
)

if ENABLE_FEATURE:
    server.shell(
        name='Enable feature',
        commands=['my-package --enable-feature'],
    )