115 lines
3.9 KiB
JavaScript
115 lines
3.9 KiB
JavaScript
/**
|
|
* Dynamic dependency resolution for cubes
|
|
* @module cubes/dependencies
|
|
*/
|
|
import { getLogger } from '@logtape/logtape';
|
|
import { VariableAssignment } from '../nopy.prompts.js';
|
|
const log = getLogger(['nopy', 'resolution']);
|
|
/**
|
|
* Context for the resolution process
|
|
*/
|
|
export class BuildContext {
|
|
allCubes;
|
|
variables;
|
|
session;
|
|
config;
|
|
auth;
|
|
options;
|
|
deployCalls = [];
|
|
cubeSessions = [];
|
|
resolvedCubes = new Set();
|
|
constructor(allCubes, variables, session, config, auth, options = {}) {
|
|
this.allCubes = allCubes;
|
|
this.variables = variables;
|
|
this.session = session;
|
|
this.config = config;
|
|
this.auth = auth;
|
|
this.options = options;
|
|
}
|
|
/**
|
|
* Resolves a cube, its dependencies, and hooks recursively
|
|
*/
|
|
async resolveCube(cubeId, host, overrides = {}) {
|
|
const cube = this.allCubes[cubeId];
|
|
if (!cube) {
|
|
throw new Error(`Cube not found: ${cubeId}`);
|
|
}
|
|
log.debug('Resolving cube', { cubeId, host });
|
|
// 1. Assign overrides and defaults
|
|
if (Object.keys(overrides).length > 0) {
|
|
this.variables.assign(cubeId, 'params', overrides);
|
|
}
|
|
this.variables.assign(cubeId, 'defaults', cube.getDefaults());
|
|
// 2. Variable collection
|
|
if (this.options.isSessionReplay) {
|
|
const sessionCube = this.session.cubes.find(c => c.key === cubeId);
|
|
if (sessionCube) {
|
|
this.variables.assign(cubeId, 'defaults', sessionCube.variables);
|
|
}
|
|
}
|
|
else {
|
|
await VariableAssignment(cube, this.variables);
|
|
}
|
|
const currentVars = this.variables.get(cubeId);
|
|
const hookCtx = {
|
|
exec: (id, vars) => this.resolveCube(id, host, vars),
|
|
};
|
|
// 3. Execute 'before' hooks
|
|
if (cube.manifest.before) {
|
|
for (const hook of cube.manifest.before) {
|
|
await hook(hookCtx, currentVars);
|
|
}
|
|
}
|
|
// 4. Resolve dynamic dependencies
|
|
const depSpecs = cube.manifest.dependencies?.(currentVars) ?? [];
|
|
for (const spec of depSpecs) {
|
|
const depId = typeof spec === 'string' ? spec : spec[0];
|
|
const depVars = typeof spec === 'string' ? {} : (spec[1] ?? {});
|
|
await this.resolveCube(depId, host, depVars);
|
|
}
|
|
// 5. Build the deployment call
|
|
this.buildDeployCall(cube, host);
|
|
// 6. Execute 'after' hooks
|
|
if (cube.manifest.after) {
|
|
for (const hook of cube.manifest.after) {
|
|
await hook(hookCtx, currentVars);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Builds and stores a deployment call for a resolved cube
|
|
*/
|
|
buildDeployCall(cube, host) {
|
|
const cubeId = cube.id;
|
|
const callKey = `${cubeId}:${host}`;
|
|
if (this.resolvedCubes.has(callKey))
|
|
return;
|
|
const parts = [];
|
|
if (this.auth.method === 'password' && this.auth.username && this.auth.password) {
|
|
parts.push(`--user ${this.auth.username} --password ${this.auth.password}`);
|
|
}
|
|
const cubeVars = this.variables.get(cubeId);
|
|
Object.entries(cubeVars).forEach(([key, value]) => {
|
|
parts.push(`--data "${key}=${value}"`);
|
|
});
|
|
parts.push(`--chdir ${cube.dir}`);
|
|
parts.push(`${cube.dir}/${cube.deployScript}`);
|
|
const command = ['pyinfra', host, '-y', ...parts];
|
|
this.deployCalls.push({
|
|
cube: cubeId,
|
|
host,
|
|
cwd: cube.dir,
|
|
command,
|
|
env: cubeVars,
|
|
dependencies: [],
|
|
});
|
|
if (!this.cubeSessions.some(s => s.key === cubeId)) {
|
|
this.cubeSessions.push({
|
|
key: cubeId,
|
|
variables: this.variables.get(cubeId, 'prompts'),
|
|
});
|
|
}
|
|
this.resolvedCubes.add(callKey);
|
|
}
|
|
}
|