[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
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
MJS Format (.session.mjs) - Recommended
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:
- Rename the file from
.session.jsonto.session.mjs - Add
export defaultbefore the configuration object - Remove quotes from property keys (optional)
- 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
- Use MJS for new sessions - Take advantage of comments and flexibility
- Document your cubes - Add comments explaining what each cube does
- Use environment variables - Make sessions reusable across environments
- Extract common config - Share configuration across multiple sessions
- Version control - Both formats work well with git, but a recorded session holds every value its run used; read one before committing it
- Validate at runtime - The loader validates the structure regardless of format
- 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.