Files
ansiblings/packages/nopy/docs/SESSION_FORMAT.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

7.8 KiB

Nopy Session Format

Nopy supports two session file formats: JSON and MJS (ES Module JavaScript).

Supported Formats

JSON Format (.session.json)

Traditional JSON format for session files:

{
  "version": "1.0.0",
  "timestamp": "2025-10-15T00:00:00.000Z",
  "cubes": [
    {
      "key": "runtime:nodevm",
      "variables": {
        "VERSION": "22",
        "USER": "myuser"
      }
    }
  ],
  "hosts": ["@ssh/myhost.local"],
  "auth": {
    "method": "password",
    "username": "admin"
  },
  "env": {}
}

Limitations:

  • No comments allowed (pure JSON)
  • Cannot use dynamic values or computation
  • No code reuse or imports

JavaScript module format with full ES Module support:

// Nopy Session Configuration
// Comments are fully supported!

// You can import values from other files
import { commonHosts } from './common-config.mjs';

// You can use dynamic values
const timestamp = new Date().toISOString();
const nodeVersion = process.env.NODE_VERSION || "22";

export default {
  version: "1.0.0",
  timestamp,

  cubes: [
    // Inline comments for each cube
    {
      key: "runtime:nodevm",
      variables: {
        VERSION: nodeVersion,  // Dynamic value
        USER: "myuser",
        ALIAS: "nodelts",
        GLOBAL_PACKAGES: "pm2 yarn"
      }
    },

    // Add more cubes...
  ],

  hosts: commonHosts,  // Imported from another file

  auth: {
    method: "password",
    username: "admin"
  },

  env: {
    NODE_ENV: process.env.NODE_ENV || "production"
  }
};

Advantages:

  • Comments - Document your configuration inline
  • Dynamic values - Use environment variables, compute values
  • Code reuse - Import common configurations from other files
  • Parameterization - Easily parameterize sessions from external tools
  • Type safety - Use JSDoc or TypeScript for validation
  • Computation - Calculate values, filter arrays, etc.

Advanced MJS Examples

Using Environment Variables

export default {
  version: "1.0.0",
  timestamp: new Date().toISOString(),

  cubes: [
    {
      key: "typestack-install",
      variables: {
        REPO: process.env.GIT_REPO || "git@github.com:org/repo.git",
        USER: process.env.DEPLOY_USER || "admin",
        APP: process.env.APP_NAME || "myapp",
        ENV: process.env.NODE_ENV || "production"
      }
    }
  ],

  hosts: [process.env.TARGET_HOST || "@ssh/localhost"],

  auth: {
    method: "password",
    username: process.env.SSH_USER || "admin"
  }
};

Conditional Cube Inclusion

const isDevelopment = process.env.NODE_ENV === 'development';

export default {
  version: "1.0.0",
  timestamp: new Date().toISOString(),

  cubes: [
    {
      key: "runtime:docker",
      variables: { DISTRO: "debian" }
    },

    // Only include in development
    ...(isDevelopment ? [{
      key: "debug-tools",
      variables: { INSTALL_GDB: true }
    }] : [])
  ],

  hosts: ["@ssh/myhost.local"],
  auth: { method: "ssh" }
};

Importing Common Configuration

common-config.mjs:

export const productionHosts = [
  "@ssh/prod-server-1.local",
  "@ssh/prod-server-2.local"
];

export const stagingHosts = [
  "@ssh/staging.local"
];

export const commonCubes = [
  {
    key: "apt:essentials",
    variables: { UPDATE: true }
  },
  {
    key: "runtime:docker",
    variables: { DISTRO: "debian" }
  }
];

my-session.session.mjs:

import { productionHosts, commonCubes } from './common-config.mjs';

export default {
  version: "1.0.0",
  timestamp: new Date().toISOString(),

  cubes: [
    ...commonCubes,  // Include common cubes
    {
      key: "typestack-install",
      variables: {
        REPO: "git@github.com:myorg/myapp.git",
        USER: "appuser",
        APP: "myapp"
      }
    }
  ],

  hosts: productionHosts,  // Use imported hosts

  auth: {
    method: "password",
    username: "admin"
  }
};

Programmatic Generation

You can even generate sessions programmatically from other tools:

generate-session.mjs:

import fs from 'fs';

function generateSession(config) {
  const cubes = config.services.map(service => ({
    key: "typestack-install",
    variables: {
      REPO: service.repo,
      USER: config.user,
      APP: service.name,
      ENV: config.environment
    }
  }));

  const session = {
    version: "1.0.0",
    timestamp: new Date().toISOString(),
    cubes,
    hosts: config.hosts,
    auth: {
      method: "password",
      username: config.user
    }
  };

  const content = `export default ${JSON.stringify(session, null, 2)};`;
  fs.writeFileSync('generated.session.mjs', content);
}

// Generate from external configuration
generateSession({
  user: "deploy",
  environment: "production",
  services: [
    { name: "api", repo: "git@github.com:org/api.git" },
    { name: "web", repo: "git@github.com:org/web.git" }
  ],
  hosts: ["@ssh/prod.local"]
});

Loading Sessions

Both formats are loaded the same way:

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

// Load JSON
const jsonSession = await loadSession('./my-session.session.json');

// Load MJS
const mjsSession = await loadSession('./my-session.session.mjs');

The file extension determines which loader to use.

Migration from JSON to MJS

To convert an existing JSON session to MJS:

  1. Rename the file from .session.json to .session.mjs
  2. Add export default before the configuration object
  3. Remove quotes from property keys (optional)
  4. Add comments and dynamic values as needed

Before (JSON):

{
  "version": "1.0.0",
  "cubes": [...]
}

After (MJS):

export default {
  version: "1.0.0",
  cubes: [...]
};

Best Practices

  1. Use MJS for new sessions - Take advantage of comments and flexibility
  2. Document your cubes - Add comments explaining what each cube does
  3. Use environment variables - Make sessions reusable across environments
  4. Extract common config - Share configuration across multiple sessions
  5. Version control - Both formats work well with git, but a recorded session holds every value its run used; read one before committing it
  6. Validate at runtime - The loader validates the structure regardless of format
  7. Leave secrets out - Declare them in the manifest instead, and let the replay ask

Session Schema

Both formats must export/contain an object with this structure:

interface NopySession {
  version: string;              // Session format version
  timestamp: string;            // ISO timestamp
  cubes: CubeSession[];        // Array of cube configurations
  hosts: string[];             // Target hosts
  auth: AuthSession;           // Authentication configuration
  env?: Record<string, any>;   // Global environment variables
}

interface CubeSession {
  key: string;                 // Cube identifier
  variables: Record<string, any>; // Cube-specific variables
}

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

A session nopy writes holds, per cube, every value that cube ran with — what was typed, what came from .nopyrc.json, what a dependency supplied, and what fell through to the schema's .default(). Two things are deliberately absent and are asked for again on replay: the SSH password, and any key the cube's manifest listed under secrets.

A session you write by hand is under no such obligation — variables may hold as few keys as you like, and anything missing resolves the usual way. Note that a key declared a secret is prompted for whether or not the session carries a value: writing one in only pre-fills the prompt, it does not skip it. The variable form shows what it is editing, so a secret you put in a session file appears on screen as well as on disk.