dank-ai 1.0.5 ā 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1692 -65
- package/bin/dank +15 -0
- package/lib/agent.js +27 -0
- package/lib/cli/production-build.js +110 -0
- package/lib/docker/manager.js +72 -0
- package/package.json +1 -1
package/bin/dank
CHANGED
|
@@ -75,6 +75,21 @@ program
|
|
|
75
75
|
await buildCommand(options);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// Production build command - build and optionally push production images
|
|
79
|
+
program
|
|
80
|
+
.command('build:prod')
|
|
81
|
+
.description('Build production Docker images with custom naming and tagging')
|
|
82
|
+
.option('-c, --config <file>', 'Configuration file path', 'dank.config.js')
|
|
83
|
+
.option('--push', 'Push images to registry after building')
|
|
84
|
+
.option('--tag <tag>', 'Custom tag for images (default: latest)')
|
|
85
|
+
.option('--registry <registry>', 'Docker registry URL (e.g., docker.io, ghcr.io)')
|
|
86
|
+
.option('--namespace <namespace>', 'Docker namespace/organization')
|
|
87
|
+
.option('--force', 'Force rebuild without cache')
|
|
88
|
+
.action(async (options) => {
|
|
89
|
+
const { productionBuildCommand } = require('../lib/cli/production-build');
|
|
90
|
+
await productionBuildCommand(options);
|
|
91
|
+
});
|
|
92
|
+
|
|
78
93
|
// Clean command - cleanup Docker resources
|
|
79
94
|
program
|
|
80
95
|
.command('clean')
|
package/lib/agent.js
CHANGED
|
@@ -500,6 +500,27 @@ class DankAgent {
|
|
|
500
500
|
return this;
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Set agent image configuration for Docker builds
|
|
505
|
+
*/
|
|
506
|
+
setAgentImageConfig(options = {}) {
|
|
507
|
+
const schema = Joi.object({
|
|
508
|
+
registry: Joi.string().optional(),
|
|
509
|
+
namespace: Joi.string().optional(),
|
|
510
|
+
tag: Joi.string().default('latest')
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const { error, value } = schema.validate(options);
|
|
514
|
+
if (error) {
|
|
515
|
+
throw new Error(`Invalid agent image configuration: ${error.message}`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
this.config.agentImage = value;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
503
524
|
/**
|
|
504
525
|
* Get the complete agent configuration for serialization
|
|
505
526
|
*/
|
|
@@ -589,6 +610,12 @@ class DankAgent {
|
|
|
589
610
|
|
|
590
611
|
environment: Joi.object().pattern(Joi.string(), Joi.string()).default({}),
|
|
591
612
|
|
|
613
|
+
agentImage: Joi.object({
|
|
614
|
+
registry: Joi.string().optional(),
|
|
615
|
+
namespace: Joi.string().optional(),
|
|
616
|
+
tag: Joi.string().default('latest')
|
|
617
|
+
}).optional(),
|
|
618
|
+
|
|
592
619
|
custom: Joi.object().default({}),
|
|
593
620
|
|
|
594
621
|
tools: Joi.object({
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Production Build Command - Build and optionally push production Docker images
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { DockerManager } = require('../docker/manager');
|
|
9
|
+
|
|
10
|
+
async function productionBuildCommand(options) {
|
|
11
|
+
try {
|
|
12
|
+
console.log(chalk.yellow('šļø Building production Docker images...\n'));
|
|
13
|
+
|
|
14
|
+
// Load configuration
|
|
15
|
+
const configPath = path.resolve(options.config);
|
|
16
|
+
if (!await fs.pathExists(configPath)) {
|
|
17
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const config = require(configPath);
|
|
21
|
+
if (!config.agents || !Array.isArray(config.agents)) {
|
|
22
|
+
throw new Error('No agents found in configuration');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Initialize Docker manager
|
|
26
|
+
const dockerManager = new DockerManager();
|
|
27
|
+
await dockerManager.initialize();
|
|
28
|
+
|
|
29
|
+
// Build production images for each agent
|
|
30
|
+
const buildResults = [];
|
|
31
|
+
for (const agent of config.agents) {
|
|
32
|
+
try {
|
|
33
|
+
console.log(chalk.blue(`š¦ Building production image for agent: ${agent.name}`));
|
|
34
|
+
|
|
35
|
+
// Use agent's image config if available, otherwise use CLI options
|
|
36
|
+
const agentImageConfig = agent.config?.agentImage || {};
|
|
37
|
+
const buildOptions = {
|
|
38
|
+
tag: options.tag || agentImageConfig.tag || 'latest',
|
|
39
|
+
registry: options.registry || agentImageConfig.registry,
|
|
40
|
+
namespace: options.namespace || agentImageConfig.namespace,
|
|
41
|
+
force: options.force || false,
|
|
42
|
+
push: options.push || false
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = await dockerManager.buildProductionImage(agent, buildOptions);
|
|
46
|
+
|
|
47
|
+
buildResults.push({
|
|
48
|
+
agent: agent.name,
|
|
49
|
+
imageName: result.imageName,
|
|
50
|
+
success: true,
|
|
51
|
+
pushed: result.pushed || false
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(chalk.green(`ā
Successfully built: ${result.imageName}`));
|
|
55
|
+
if (result.pushed) {
|
|
56
|
+
console.log(chalk.green(`š Successfully pushed: ${result.imageName}`));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red(`ā Failed to build agent ${agent.name}:`), error.message);
|
|
61
|
+
buildResults.push({
|
|
62
|
+
agent: agent.name,
|
|
63
|
+
success: false,
|
|
64
|
+
error: error.message
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Print summary
|
|
70
|
+
console.log(chalk.yellow('\nš Build Summary:'));
|
|
71
|
+
console.log(chalk.gray('================'));
|
|
72
|
+
|
|
73
|
+
const successful = buildResults.filter(r => r.success);
|
|
74
|
+
const failed = buildResults.filter(r => !r.success);
|
|
75
|
+
const pushed = buildResults.filter(r => r.pushed);
|
|
76
|
+
|
|
77
|
+
console.log(chalk.green(`ā
Successful builds: ${successful.length}`));
|
|
78
|
+
if (pushed.length > 0) {
|
|
79
|
+
console.log(chalk.blue(`š Pushed to registry: ${pushed.length}`));
|
|
80
|
+
}
|
|
81
|
+
if (failed.length > 0) {
|
|
82
|
+
console.log(chalk.red(`ā Failed builds: ${failed.length}`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// List built images
|
|
86
|
+
if (successful.length > 0) {
|
|
87
|
+
console.log(chalk.cyan('\nš¦ Built Images:'));
|
|
88
|
+
successful.forEach(result => {
|
|
89
|
+
console.log(chalk.gray(` - ${result.imageName}`));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// List failed builds
|
|
94
|
+
if (failed.length > 0) {
|
|
95
|
+
console.log(chalk.red('\nā Failed Builds:'));
|
|
96
|
+
failed.forEach(result => {
|
|
97
|
+
console.log(chalk.gray(` - ${result.agent}: ${result.error}`));
|
|
98
|
+
});
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(chalk.green('\nš Production build completed successfully!'));
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(chalk.red('ā Production build failed:'), error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { productionBuildCommand };
|
package/lib/docker/manager.js
CHANGED
|
@@ -413,6 +413,78 @@ class DockerManager {
|
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Build production image with custom naming and tagging
|
|
418
|
+
*/
|
|
419
|
+
async buildProductionImage(agent, options = {}) {
|
|
420
|
+
const {
|
|
421
|
+
tag = 'latest',
|
|
422
|
+
registry,
|
|
423
|
+
namespace,
|
|
424
|
+
force = false,
|
|
425
|
+
push = false
|
|
426
|
+
} = options;
|
|
427
|
+
|
|
428
|
+
// Construct production image name
|
|
429
|
+
let imageName = agent.name.toLowerCase();
|
|
430
|
+
|
|
431
|
+
// Add namespace if provided
|
|
432
|
+
if (namespace) {
|
|
433
|
+
imageName = `${namespace}/${imageName}`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Add registry if provided
|
|
437
|
+
if (registry) {
|
|
438
|
+
imageName = `${registry}/${imageName}`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Add tag
|
|
442
|
+
imageName = `${imageName}:${tag}`;
|
|
443
|
+
|
|
444
|
+
this.logger.info(`Building production image for agent: ${agent.name} -> ${imageName}`);
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const buildContext = await this.createAgentBuildContext(agent);
|
|
448
|
+
|
|
449
|
+
const stream = await this.docker.buildImage(buildContext, {
|
|
450
|
+
t: imageName,
|
|
451
|
+
dockerfile: 'Dockerfile',
|
|
452
|
+
nocache: force
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await this.followBuildProgress(stream, `Production build for ${agent.name}`);
|
|
456
|
+
|
|
457
|
+
this.logger.info(`Production image '${imageName}' built successfully`);
|
|
458
|
+
|
|
459
|
+
// Clean up build context
|
|
460
|
+
await fs.remove(buildContext);
|
|
461
|
+
|
|
462
|
+
let pushed = false;
|
|
463
|
+
|
|
464
|
+
// Push to registry if requested
|
|
465
|
+
if (push) {
|
|
466
|
+
try {
|
|
467
|
+
this.logger.info(`Pushing image to registry: ${imageName}`);
|
|
468
|
+
const pushStream = await this.docker.getImage(imageName).push();
|
|
469
|
+
await this.followBuildProgress(pushStream, `Push ${imageName}`);
|
|
470
|
+
this.logger.info(`Successfully pushed image: ${imageName}`);
|
|
471
|
+
pushed = true;
|
|
472
|
+
} catch (pushError) {
|
|
473
|
+
this.logger.warn(`Failed to push image ${imageName}: ${pushError.message}`);
|
|
474
|
+
// Don't fail the build if push fails
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
imageName,
|
|
480
|
+
pushed
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
} catch (error) {
|
|
484
|
+
throw new Error(`Failed to build production image: ${error.message}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
416
488
|
/**
|
|
417
489
|
* Generate handlers code from agent configuration
|
|
418
490
|
*/
|