dhti-cli 0.1.0 → 0.1.1
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/dist/commands/compose.d.ts +14 -0
- package/dist/commands/compose.js +112 -0
- package/dist/commands/conch.d.ts +19 -0
- package/dist/commands/conch.js +119 -0
- package/dist/commands/docker.d.ts +16 -0
- package/dist/commands/docker.js +76 -0
- package/dist/commands/elixir.d.ts +20 -0
- package/dist/commands/elixir.js +129 -0
- package/dist/commands/mimic.d.ts +9 -0
- package/dist/commands/mimic.js +156 -0
- package/dist/commands/synthetic.d.ts +17 -0
- package/dist/commands/synthetic.js +88 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/utils/bootstrap.d.ts +3 -0
- package/dist/utils/bootstrap.js +57 -0
- package/dist/utils/card.d.ts +58 -0
- package/dist/utils/card.js +69 -0
- package/dist/utils/chain.d.ts +5 -0
- package/dist/utils/chain.js +17 -0
- package/dist/utils/getCard.d.ts +3 -0
- package/dist/utils/getCard.js +11 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/request.d.ts +30 -0
- package/dist/utils/request.js +34 -0
- package/dist/utils/useDhti.d.ts +5 -0
- package/dist/utils/useDhti.js +21 -0
- package/oclif.manifest.json +442 -2
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Compose extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
module: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
static init: () => void;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
export default class Compose extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
op: Args.string({ description: 'Operation to perform (add, delete, read or reset)' }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Generates a docker-compose.yml file from a list of modules';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> <%= command.id %>',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
file: Flags.string({ char: 'f', default: `${os.homedir()}/dhti/docker-compose.yml`, description: 'Full path to the docker compose file to read from. Creates if it does not exist' }),
|
|
15
|
+
// flag with a value (-n, --name=VALUE)
|
|
16
|
+
module: Flags.string({ char: 'm', description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir)', multiple: true }),
|
|
17
|
+
};
|
|
18
|
+
static init = () => {
|
|
19
|
+
// Create ${os.homedir()}/dhti if it does not exist
|
|
20
|
+
if (!fs.existsSync(`${os.homedir()}/dhti`)) {
|
|
21
|
+
fs.mkdirSync(`${os.homedir()}/dhti`);
|
|
22
|
+
}
|
|
23
|
+
// Create ${os.homedir()}/dhti/docker-compose.yml if it does not exist
|
|
24
|
+
if (!fs.existsSync(`${os.homedir()}/dhti/docker-compose.yml`)) {
|
|
25
|
+
fs.writeFileSync(`${os.homedir()}/dhti/docker-compose.yml`, '', 'utf8');
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(Compose);
|
|
30
|
+
// console.log('args', args) //args { op: 'add' }
|
|
31
|
+
// console.log('flags', flags) //flags { module: [ 'default', 'langserve', 'redis' ] }
|
|
32
|
+
const openmrs = ['gateway', 'frontend', 'backend', 'openmrs-db'];
|
|
33
|
+
const langserve = ['langserve'];
|
|
34
|
+
const ollama = ['ollama', 'ollama-webui'];
|
|
35
|
+
const langfuse = ['langfuse', 'postgres-db'];
|
|
36
|
+
const cqlFhir = ['fhir', 'postgres-db', 'cql-elm', 'cql-web'];
|
|
37
|
+
const redis = ['redis', 'redis-commander'];
|
|
38
|
+
const neo4j = ['neo4j', 'fhirg'];
|
|
39
|
+
const gateway = ['gateway'];
|
|
40
|
+
const webui = ['ollama-webui'];
|
|
41
|
+
const fhir = ['fhir', 'postgres-db'];
|
|
42
|
+
const mcpFhir = ['mcp-fhir', 'fhir', 'postgres-db'];
|
|
43
|
+
const _modules = {
|
|
44
|
+
cqlFhir,
|
|
45
|
+
fhir,
|
|
46
|
+
gateway,
|
|
47
|
+
langfuse,
|
|
48
|
+
langserve,
|
|
49
|
+
mcpFhir,
|
|
50
|
+
neo4j,
|
|
51
|
+
ollama,
|
|
52
|
+
openmrs,
|
|
53
|
+
redis,
|
|
54
|
+
webui
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const masterData = yaml.load(fs.readFileSync('src/resources/docker-compose-master.yml', 'utf8'));
|
|
58
|
+
let existingData = { services: {}, version: '3.8' };
|
|
59
|
+
if (fs.existsSync(flags.file)) {
|
|
60
|
+
existingData = yaml.load(fs.readFileSync(flags.file, 'utf8'));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
Compose.init(); // Create the file if it does not exist
|
|
64
|
+
}
|
|
65
|
+
// Echo the existing data to the console
|
|
66
|
+
if (args.op === 'read') {
|
|
67
|
+
console.log(existingData);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Delete flags.file if args.op is reset
|
|
71
|
+
if (args.op === 'reset') {
|
|
72
|
+
fs.unlinkSync(flags.file);
|
|
73
|
+
Compose.init(); // Recreate the file
|
|
74
|
+
}
|
|
75
|
+
// if existing data is not null and arg is delete, remove the modules from the existing data
|
|
76
|
+
if (Object.keys(existingData.services).length > 0 && args.op === 'delete') {
|
|
77
|
+
// Expand the modules to remove using _modules object
|
|
78
|
+
let modulesToDelete = [];
|
|
79
|
+
for (const module of flags.module ?? []) {
|
|
80
|
+
modulesToDelete = modulesToDelete.concat(_modules[module]);
|
|
81
|
+
}
|
|
82
|
+
for (const module of modulesToDelete ?? []) {
|
|
83
|
+
if (existingData.services[module]) {
|
|
84
|
+
delete existingData.services[module];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// if arg is add, add the modules from the master data
|
|
89
|
+
if (args.op === 'add') {
|
|
90
|
+
// Expand the modules to add using _modules object
|
|
91
|
+
let modulesToAdd = [];
|
|
92
|
+
for (const module of flags.module ?? []) {
|
|
93
|
+
modulesToAdd = modulesToAdd.concat(_modules[module]);
|
|
94
|
+
}
|
|
95
|
+
for (const module of modulesToAdd ?? []) {
|
|
96
|
+
existingData.services[module] = masterData.services[module];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Add all volumes from master data to existing data by default
|
|
100
|
+
existingData.volumes = {};
|
|
101
|
+
for (const key of Object.keys(masterData.volumes)) {
|
|
102
|
+
existingData.volumes[key] = masterData.volumes[key];
|
|
103
|
+
}
|
|
104
|
+
const toWrite = yaml.dump(existingData).replaceAll('null', '');
|
|
105
|
+
console.log('Writing file:', toWrite);
|
|
106
|
+
fs.writeFileSync(flags.file, toWrite, 'utf8');
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Conch extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
image: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
repoVersion: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { exec } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
export default class Conch extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Install or uninstall conchs to create a Docker image';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> <%= command.id %>',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
branch: Flags.string({ char: 'b', default: "develop", description: 'Branch to install from' }),
|
|
15
|
+
container: Flags.string({ char: 'c', default: "dhti-frontend-1", description: 'Name of the container to copy the conch to while in dev mode' }),
|
|
16
|
+
dev: Flags.string({ char: 'd', default: "none", description: 'Dev folder to install' }),
|
|
17
|
+
git: Flags.string({ char: 'g', default: "none", description: 'Github repository to install' }),
|
|
18
|
+
image: Flags.string({ char: 'i', default: "openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17", description: 'Base image to use for the conch' }),
|
|
19
|
+
name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
|
|
20
|
+
repoVersion: Flags.string({ char: 'v', default: "1.0.0", description: 'Version of the conch' }),
|
|
21
|
+
workdir: Flags.string({ char: 'w', default: `${os.homedir()}/dhti`, description: 'Working directory to install the conch' }),
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
const { args, flags } = await this.parse(Conch);
|
|
25
|
+
if (!flags.name) {
|
|
26
|
+
console.log("Please provide a name for the conch");
|
|
27
|
+
this.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// if arg is dev then copy to docker as below
|
|
30
|
+
// docker cp ../../openmrs-esm-genai/dist/. dhti-frontend-1:/usr/share/nginx/html/openmrs-esm-genai-1.0.0
|
|
31
|
+
// docker restart dhti-frontend-1
|
|
32
|
+
if (args.op === 'dev') {
|
|
33
|
+
console.log(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`);
|
|
34
|
+
try {
|
|
35
|
+
exec(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`, (error, stdout, stderr) => {
|
|
36
|
+
if (error) {
|
|
37
|
+
console.error(`exec error: ${error}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(`stdout: ${stdout}`);
|
|
41
|
+
console.error(`stderr: ${stderr}`);
|
|
42
|
+
});
|
|
43
|
+
exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
console.error(`exec error: ${error}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(`stdout: ${stdout}`);
|
|
49
|
+
console.error(`stderr: ${stderr}`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.log("Error copying conch to container", error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Create a directory to install the elixir
|
|
57
|
+
if (!fs.existsSync(`${flags.workdir}/conch`)) {
|
|
58
|
+
fs.mkdirSync(`${flags.workdir}/conch`);
|
|
59
|
+
}
|
|
60
|
+
fs.cpSync('src/resources/spa', `${flags.workdir}/conch`, { recursive: true });
|
|
61
|
+
// Rewrite files
|
|
62
|
+
const rewrite = () => {
|
|
63
|
+
flags.name = flags.name ?? 'openmrs-esm-genai';
|
|
64
|
+
// Read and process importmap.json
|
|
65
|
+
const importmap = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/importmap.json`, 'utf8'));
|
|
66
|
+
if (args.op === 'install')
|
|
67
|
+
importmap.imports[flags.name.replace('openmrs-', '@openmrs/')] = `./${flags.name}-${flags.repoVersion}/${flags.name}.js`;
|
|
68
|
+
if (args.op === 'uninstall')
|
|
69
|
+
delete importmap.imports[flags.name.replace('openmrs-', '@openmrs/')];
|
|
70
|
+
fs.writeFileSync(`${flags.workdir}/conch/def/importmap.json`, JSON.stringify(importmap, null, 2));
|
|
71
|
+
// Read and process spa-assemble-config.json
|
|
72
|
+
const spaAssembleConfig = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, 'utf8'));
|
|
73
|
+
if (args.op === 'install')
|
|
74
|
+
spaAssembleConfig.frontendModules[flags.name.replace('openmrs-', '@openmrs/')] = `${flags.repoVersion}`;
|
|
75
|
+
if (args.op === 'uninstall')
|
|
76
|
+
delete spaAssembleConfig.frontendModules[flags.name.replace('openmrs-', '@openmrs/')];
|
|
77
|
+
fs.writeFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, JSON.stringify(spaAssembleConfig, null, 2));
|
|
78
|
+
// Read and process Dockerfile
|
|
79
|
+
let dockerfile = fs.readFileSync(`${flags.workdir}/conch/Dockerfile`, 'utf8');
|
|
80
|
+
dockerfile = dockerfile.replaceAll('conch', flags.name).replaceAll('version', flags.repoVersion).replaceAll('server-image', flags.image);
|
|
81
|
+
fs.writeFileSync(`${flags.workdir}/conch/Dockerfile`, dockerfile);
|
|
82
|
+
// Read routes.json
|
|
83
|
+
const routes = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/${flags.name}/src/routes.json`, 'utf8'));
|
|
84
|
+
// Add to routes.registry.json
|
|
85
|
+
const registry = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/routes.registry.json`, 'utf8'));
|
|
86
|
+
if (args.op === 'install')
|
|
87
|
+
registry[(flags.name).replace('openmrs-', '@openmrs/')] = routes;
|
|
88
|
+
if (args.op === 'uninstall')
|
|
89
|
+
delete registry[(flags.name).replace('openmrs-', '@openmrs/')];
|
|
90
|
+
fs.writeFileSync(`${flags.workdir}/conch/def/routes.registry.json`, JSON.stringify(registry, null, 2));
|
|
91
|
+
};
|
|
92
|
+
if (flags.git !== 'none') {
|
|
93
|
+
// git clone the repository
|
|
94
|
+
exec(`git clone ${flags.git} ${flags.workdir}/conch/${flags.name}`, (error, stdout, stderr) => {
|
|
95
|
+
if (error) {
|
|
96
|
+
console.error(`exec error: ${error}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Checkout the branch
|
|
100
|
+
exec(`cd ${flags.workdir}/conch/${flags.name} && git checkout ${flags.branch}`, (error, stdout, stderr) => {
|
|
101
|
+
if (error) {
|
|
102
|
+
console.error(`exec error: ${error}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
rewrite();
|
|
106
|
+
console.log(`stdout: ${stdout}`);
|
|
107
|
+
console.error(`stderr: ${stderr}`);
|
|
108
|
+
});
|
|
109
|
+
console.log(`stdout: ${stdout}`);
|
|
110
|
+
console.error(`stderr: ${stderr}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// If flags.dev is not none, copy the dev folder to the conch directory
|
|
114
|
+
if (flags.dev !== 'none' && args.op !== 'dev') {
|
|
115
|
+
fs.cpSync(flags.dev, `${flags.workdir}/conch/${flags.name}`, { recursive: true });
|
|
116
|
+
rewrite();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Docker extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
path: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
down: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
up: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
export default class Docker extends Command {
|
|
8
|
+
static args = {
|
|
9
|
+
path: Args.string({ default: `${os.homedir()}/dhti`, description: 'Docker project path to build. Ex: dhti' }),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Build a docker project and update docker-compose file';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %>',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
down: Flags.boolean({ char: 'd', default: false, description: 'Run docker-compose down after building' }),
|
|
17
|
+
file: Flags.string({ char: 'f', default: `${os.homedir()}/dhti/docker-compose.yml`, description: 'Full path to the docker compose file to edit or run.' }),
|
|
18
|
+
name: Flags.string({ char: 'n', description: 'Name of the container to build' }),
|
|
19
|
+
type: Flags.string({ char: 't', default: 'elixir', description: 'Type of the service (elixir/conch)' }),
|
|
20
|
+
up: Flags.boolean({ char: 'u', default: false, description: 'Run docker-compose up after building' }),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { args, flags } = await this.parse(Docker);
|
|
24
|
+
if (flags.up) {
|
|
25
|
+
exec(`docker compose -f ${flags.file} up -d`, (error, stdout, stderr) => {
|
|
26
|
+
if (error) {
|
|
27
|
+
console.error(`exec error: ${error}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log(`stdout: ${stdout}`);
|
|
31
|
+
console.error(`stderr: ${stderr}`);
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (flags.down) {
|
|
36
|
+
exec(`docker compose -f ${flags.file} down`, (error, stdout, stderr) => {
|
|
37
|
+
if (error) {
|
|
38
|
+
console.error(`exec error: ${error}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(`stdout: ${stdout}`);
|
|
42
|
+
console.error(`stderr: ${stderr}`);
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!flags.name && (!flags.up || !flags.down)) {
|
|
47
|
+
console.log("Please provide a name for the container to build");
|
|
48
|
+
this.exit(1);
|
|
49
|
+
}
|
|
50
|
+
// cd to path, docker build tag with name
|
|
51
|
+
const spinner = ora('Running docker build ..').start();
|
|
52
|
+
exec(`cd ${args.path}/${flags.type} && docker build -t ${flags.name} . > /dev/null 2>&1`, (error, stdout, stderr) => {
|
|
53
|
+
if (error) {
|
|
54
|
+
spinner.fail('Docker build failed');
|
|
55
|
+
console.error(`exec error: ${error}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
spinner.succeed('Docker build successful');
|
|
59
|
+
console.log(`stdout: ${stdout}`);
|
|
60
|
+
console.error(`stderr: ${stderr}`);
|
|
61
|
+
});
|
|
62
|
+
// read the docker-compose file
|
|
63
|
+
const dockerCompose = yaml.load(fs.readFileSync(flags.file, 'utf8'));
|
|
64
|
+
// if type is elixir set image of backend to name, else set image of frontend to name
|
|
65
|
+
if (flags.type === 'elixir') {
|
|
66
|
+
dockerCompose.services.langserve.image = flags.name;
|
|
67
|
+
dockerCompose.services.langserve.pull_policy = "if_not_present";
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
dockerCompose.services.frontend.image = flags.name;
|
|
71
|
+
dockerCompose.services.frontend.pull_policy = "if_not_present";
|
|
72
|
+
}
|
|
73
|
+
// write the docker-compose file
|
|
74
|
+
fs.writeFileSync(flags.file, yaml.dump(dockerCompose));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Elixir extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
pypi: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
repoVersion: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
whl: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { exec } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
export default class Elixir extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Install or uninstall elixirs to create a Docker image';
|
|
11
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
12
|
+
static flags = {
|
|
13
|
+
branch: Flags.string({ char: 'b', default: 'develop', description: 'Branch to install from' }),
|
|
14
|
+
container: Flags.string({
|
|
15
|
+
char: 'c',
|
|
16
|
+
default: 'dhti-langserve-1',
|
|
17
|
+
description: 'Name of the container to copy the elixir to while in dev mode',
|
|
18
|
+
}),
|
|
19
|
+
dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
|
|
20
|
+
git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
|
|
21
|
+
name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
|
|
22
|
+
pypi: Flags.string({
|
|
23
|
+
char: 'p',
|
|
24
|
+
default: 'none',
|
|
25
|
+
description: 'PyPi package to install. Ex: dhti-elixir-base = ">=0.1.0"',
|
|
26
|
+
}),
|
|
27
|
+
repoVersion: Flags.string({ char: 'v', default: '0.1.0', description: 'Version of the elixir' }),
|
|
28
|
+
whl: Flags.string({ char: 'e', default: 'none', description: 'Whl file to install' }),
|
|
29
|
+
workdir: Flags.string({
|
|
30
|
+
char: 'w',
|
|
31
|
+
default: `${os.homedir()}/dhti`,
|
|
32
|
+
description: 'Working directory to install the elixir',
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { args, flags } = await this.parse(Elixir);
|
|
37
|
+
if (!flags.name) {
|
|
38
|
+
console.log('Please provide a name for the elixir');
|
|
39
|
+
this.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const expoName = flags.name.replaceAll('-', '_');
|
|
42
|
+
// if arg is dev then copy to docker as below
|
|
43
|
+
// docker restart dhti-langserve-1
|
|
44
|
+
if (args.op === 'dev') {
|
|
45
|
+
console.log(`cd ${flags.dev} && docker cp ${expoName}/. ${flags.container}:/app/.venv/lib/python3.11/site-packages/${expoName}`);
|
|
46
|
+
try {
|
|
47
|
+
exec(`cd ${flags.dev} && docker cp ${expoName}/. ${flags.container}:/app/.venv/lib/python3.11/site-packages/${expoName}`, (error, stdout, stderr) => {
|
|
48
|
+
if (error) {
|
|
49
|
+
console.error(`exec error: ${error}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(`stdout: ${stdout}`);
|
|
53
|
+
console.error(`stderr: ${stderr}`);
|
|
54
|
+
});
|
|
55
|
+
exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
|
|
56
|
+
if (error) {
|
|
57
|
+
console.error(`exec error: ${error}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`stdout: ${stdout}`);
|
|
61
|
+
console.error(`stderr: ${stderr}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.log('Error copying conch to container', error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Create a directory to install the elixir
|
|
69
|
+
if (!fs.existsSync(`${flags.workdir}/elixir`)) {
|
|
70
|
+
fs.mkdirSync(`${flags.workdir}/elixir`);
|
|
71
|
+
}
|
|
72
|
+
fs.cpSync('src/resources/genai', `${flags.workdir}/elixir`, { recursive: true });
|
|
73
|
+
// if whl is not none, copy the whl file to thee whl directory
|
|
74
|
+
if (flags.whl !== 'none') {
|
|
75
|
+
if (!fs.existsSync(`${flags.workdir}/elixir/whl/`)) {
|
|
76
|
+
fs.mkdirSync(`${flags.workdir}/whl/`);
|
|
77
|
+
}
|
|
78
|
+
fs.cpSync(flags.whl, `${flags.workdir}/elixir/whl/${path.basename(flags.whl)}`);
|
|
79
|
+
console.log('Installing elixir from whl file. Please modify boostrap.py file if needed');
|
|
80
|
+
}
|
|
81
|
+
// Install the elixir from git adding to the pyproject.toml file
|
|
82
|
+
let pyproject = fs.readFileSync(`${flags.workdir}/elixir/pyproject.toml`, 'utf8');
|
|
83
|
+
const originalServer = fs.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8');
|
|
84
|
+
let lineToAdd = '';
|
|
85
|
+
if (flags.whl !== 'none') {
|
|
86
|
+
lineToAdd = `${flags.name} = { file = "whl/${path.basename(flags.whl)}" }`;
|
|
87
|
+
}
|
|
88
|
+
if (flags.git !== 'none') {
|
|
89
|
+
lineToAdd = `${flags.name} = { git = "${flags.git}", branch = "${flags.branch}" }`;
|
|
90
|
+
}
|
|
91
|
+
if (flags.pypi !== 'none') {
|
|
92
|
+
lineToAdd = flags.pypi;
|
|
93
|
+
}
|
|
94
|
+
pyproject = pyproject.replace('dependencies = [', `dependencies = [\n"${flags.name}",`);
|
|
95
|
+
pyproject = pyproject.replace('[tool.uv.sources]', `[tool.uv.sources]\n${lineToAdd}\n`);
|
|
96
|
+
const newPyproject = pyproject;
|
|
97
|
+
// Add the elixir import and bootstrap to the server.py file
|
|
98
|
+
let CliImport = `from ${expoName}.bootstrap import bootstrap as ${expoName}_bootstrap\n`;
|
|
99
|
+
CliImport += `${expoName}_bootstrap()\n`;
|
|
100
|
+
CliImport += `
|
|
101
|
+
from ${expoName}.chain import DhtiChain as ${expoName}_chain_class
|
|
102
|
+
${expoName}_chain = ${expoName}_chain_class().get_chain_as_langchain_tool()
|
|
103
|
+
${expoName}_mcp_tool = ${expoName}_chain_class().get_chain_as_mcp_tool
|
|
104
|
+
mcp_server.add_tool(${expoName}_mcp_tool) # type: ignore
|
|
105
|
+
`;
|
|
106
|
+
const newCliImport = fs
|
|
107
|
+
.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
|
|
108
|
+
.replace('#DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
|
|
109
|
+
const langfuseRoute = `add_routes(app, ${expoName}_chain.with_config(config), path="/langserve/${expoName}")`;
|
|
110
|
+
const newLangfuseRoute = newCliImport.replace('#DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
|
|
111
|
+
const normalRoute = `add_routes(app, ${expoName}_chain, path="/langserve/${expoName}")`;
|
|
112
|
+
const newNormalRoute = newLangfuseRoute.replace('#DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
|
|
113
|
+
const commonRoutes = `\nadd_invokes(app, path="/langserve/${expoName}")\nadd_services(app, path="/langserve/${expoName}")`;
|
|
114
|
+
const finalRoute = newNormalRoute.replace('#DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
|
|
115
|
+
// if args.op === install, add the line to the pyproject.toml file
|
|
116
|
+
if (args.op === 'install') {
|
|
117
|
+
fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
|
|
118
|
+
fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, finalRoute);
|
|
119
|
+
}
|
|
120
|
+
if (args.op === 'uninstall') {
|
|
121
|
+
// if args.op === uninstall, remove the line from the pyproject.toml file
|
|
122
|
+
fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, pyproject.replace(lineToAdd, ''));
|
|
123
|
+
let newServer = originalServer.replace(CliImport, '');
|
|
124
|
+
newServer = newServer.replace(langfuseRoute, '');
|
|
125
|
+
newServer = newServer.replace(normalRoute, '');
|
|
126
|
+
fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, newServer);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Mimic extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
server: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|