Skip to content

Commit cc04db1

Browse files
committed
updated publish provider and create provider
1 parent 6c8c3ec commit cc04db1

22 files changed

+5793
-2
lines changed

actions/createprovider.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
const chalk = require('chalk');
2+
const axios = require('axios');
3+
const spawn = require('cross-spawn');
4+
const { Command } = require('commander');
5+
const fs = require('fs');
6+
const path = require('path');
7+
const inquirer = require('inquirer');
8+
const yaml = require('js-yaml');
9+
const { v4: uuidv4 } = require('uuid');
10+
11+
const { checkUserAuth, getUserData } = require('./userData');
12+
13+
async function askForProviderDetails(parsedYaml) {
14+
console.log(chalk.yellow(`\n------------------------------Provider Details-----------------------------------------\n`));
15+
console.log(chalk.green("Provider details help configure your provider's metadata and capabilities.\n"));
16+
17+
const providerPrompt = [
18+
{
19+
type: 'input',
20+
name: 'author',
21+
message: 'Please Enter Provider Author:',
22+
default: parsedYaml.author || 'codebolt',
23+
},
24+
{
25+
type: 'input',
26+
name: 'version',
27+
message: 'Please Enter Provider Version:',
28+
default: parsedYaml.version || '1.0.0',
29+
}
30+
];
31+
32+
const providerRes = await inquirer.prompt(providerPrompt);
33+
return {
34+
author: providerRes.author,
35+
version: providerRes.version
36+
};
37+
}
38+
39+
// Removed askForInstructions function as it's not needed for providers
40+
41+
function createProject(projectName, installPath, selectedTemplate, answers, parsedYaml ) {
42+
43+
const projectDir = path.resolve(installPath);
44+
fs.mkdirSync(projectDir, { recursive: true });
45+
46+
// Copy the selected template to the project directory
47+
const templateDir = path.resolve(__dirname,'..', 'template');
48+
const templatePath = path.join(templateDir, selectedTemplate);
49+
fs.cpSync(templatePath, projectDir, { recursive: true });
50+
51+
// Rename gitignore if it exists
52+
const gitignorePath = path.join(projectDir, 'gitignore');
53+
if (fs.existsSync(gitignorePath)) {
54+
fs.renameSync(gitignorePath, path.join(projectDir, '.gitignore'));
55+
}
56+
57+
const providerYamlPath = path.join(projectDir, 'codeboltprovider.yaml');
58+
let providerYaml = fs.readFileSync(providerYamlPath, 'utf8');
59+
60+
let providerYamlObj = yaml.load(providerYaml);
61+
providerYamlObj.name = projectName;
62+
providerYamlObj.unique_id = answers.unique_id;
63+
providerYamlObj.description = answers.providerDescription;
64+
providerYamlObj.tags = answers.tags.split(',').map(tag => tag.trim()).filter(tag => tag);
65+
providerYamlObj.author = answers.providerDetails.author;
66+
providerYamlObj.version = answers.providerDetails.version;
67+
68+
providerYaml = yaml.dump(providerYamlObj);
69+
70+
fs.writeFileSync(providerYamlPath, providerYaml, 'utf8');
71+
72+
const projectPackageJson = require(path.join(projectDir, 'package.json'));
73+
74+
// Update the project's package.json with the new project name
75+
projectPackageJson.name = projectName;
76+
77+
fs.writeFileSync(
78+
path.join(projectDir, 'package.json'),
79+
JSON.stringify(projectPackageJson, null, 2)
80+
);
81+
82+
// Run `npm install` in the project directory to install
83+
// the dependencies. We are using a third-party library
84+
// called `cross-spawn` for cross-platform support.
85+
// (Node has issues spawning child processes in Windows).
86+
spawn.sync('npm', ['install'], { stdio: 'inherit', cwd: installPath });
87+
88+
// spawn.sync('git', ['init'], { stdio: 'inherit', cwd: installPath });
89+
90+
console.log('Success! Your new provider is ready.');
91+
console.log(`Created ${projectName} at ${projectDir}`);
92+
}
93+
94+
async function getBasicAnswers(projectName, quickEnabled, parsedYaml){
95+
96+
const prompts = [];
97+
let answers = [];
98+
99+
100+
if (!quickEnabled) {
101+
prompts.push({
102+
type: 'input',
103+
name: 'projectName',
104+
message: 'Please Enter the name of your Provider:',
105+
default: projectName,
106+
});
107+
108+
prompts.push({
109+
type: 'input',
110+
name: 'unique_id',
111+
message: 'Please enter the unique_id:',
112+
default: projectName.replace(/[^a-zA-Z0-9]/g, ''),
113+
validate: function (input) {
114+
if (/\s/.test(input)) {
115+
return 'unique_id should not contain any spaces';
116+
}
117+
return true;
118+
}
119+
});
120+
121+
prompts.push({
122+
type: 'input',
123+
name: 'providerDescription',
124+
message: 'Please enter a description for your provider:',
125+
default: parsedYaml.description || '',
126+
});
127+
128+
prompts.push({
129+
type: 'input',
130+
name: 'tags',
131+
message: 'Please Enter provider tags by comma separated:',
132+
default: parsedYaml.tags ? parsedYaml.tags.join(', ') : '',
133+
});
134+
135+
answers = await inquirer.prompt(prompts);
136+
}
137+
else {
138+
answers.projectName = projectName;
139+
answers.unique_id = projectName.replace(/[^a-zA-Z0-9]/g, '')
140+
answers.providerDescription = parsedYaml.description || ''
141+
answers.tags = parsedYaml.tags ? parsedYaml.tags.join(', ') : ''
142+
}
143+
144+
145+
return answers;
146+
}
147+
148+
const createprovider = async (options) => {
149+
console.log(chalk.blue(
150+
" _____ _ _ _ _ \n"+
151+
" / __ \\ | | | | | | | \n"+
152+
" | / \\/ ___ __| | ___| |__ ___ | | |_ \n"+
153+
" | | / _ \\ / _` |/ _ \\ '_ \\ / _ \\| | __| \n"+
154+
" | \\__/\\ (_) | (_| | __/ |_) | (_) | | |_ \n"+
155+
" \\____/\\___/ \\__,_|\\___|_.__/ \\___/|_|\\__| \n"));
156+
157+
let projectName = options.name || process.argv[3];
158+
const quickEnabled = options.quick || false;
159+
160+
const providerymlpath = path.join(__dirname, '..','template/provider', 'codeboltprovider.yaml');
161+
let providerYamlData = fs.readFileSync(providerymlpath, 'utf8');
162+
const parsedYaml = yaml.load(providerYamlData);
163+
const answers = await getBasicAnswers(projectName, quickEnabled, parsedYaml)
164+
projectName = answers.projectName.trim();
165+
answers.providerDetails = !quickEnabled ? await askForProviderDetails(parsedYaml) : { author: parsedYaml.author || '', version: parsedYaml.version || '1.0.0' };
166+
167+
const installPath = path.resolve(process.cwd(), projectName);
168+
const selectedTemplate = 'provider';
169+
170+
createProject(projectName, installPath, selectedTemplate, answers, parsedYaml);
171+
};
172+
173+
174+
module.exports = { createprovider };

actions/publishProvider.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const chalk = require('chalk');
2+
const fs = require('fs');
3+
const archiver = require('archiver');
4+
const { createReadStream, createWriteStream } = require('fs');
5+
const axios = require('axios');
6+
const FormData = require('form-data');
7+
const yaml = require('js-yaml');
8+
const path = require('path');
9+
const { checkUserAuth, getUserData } = require('./userData');
10+
const { checkYamlDetails } = require('../services/yamlService');
11+
const { runBuild } = require('../services/buildService');
12+
13+
// Function to create a zip archive
14+
const createZipArchive = async (sourcePath, outputPath, ignorePatterns = []) => {
15+
return new Promise((resolve, reject) => {
16+
const output = createWriteStream(outputPath);
17+
const archive = archiver('zip', { zlib: { level: 9 } });
18+
19+
archive.on('error', err => reject(new Error(`Archive error: ${err.message}`)));
20+
21+
output.on('close', () => resolve());
22+
23+
archive.pipe(output);
24+
25+
archive.glob('**/*', {
26+
cwd: sourcePath,
27+
ignore: ignorePatterns
28+
});
29+
30+
archive.finalize();
31+
});
32+
};
33+
34+
// Function to upload file and get URL
35+
const uploadFile = async (filePath, fileType, authToken) => {
36+
const formData = new FormData();
37+
formData.append('file', fs.createReadStream(filePath));
38+
formData.append('filetype', fileType);
39+
40+
try {
41+
const response = await axios.post(
42+
'https://api.codebolt.ai/api/upload/single',
43+
formData,
44+
{
45+
headers: formData.getHeaders()
46+
}
47+
);
48+
return response.data;
49+
} catch (err) {
50+
throw new Error(`Failed to upload ${fileType}: ${err.message}`);
51+
}
52+
};
53+
54+
// Function to read and validate provider configuration
55+
const readProviderConfig = async (folderPath) => {
56+
const configPath = path.join(folderPath, 'codeboltprovider.yaml');
57+
58+
if (!fs.existsSync(configPath)) {
59+
throw new Error('codeboltprovider.yaml not found. Please run this command in a provider directory.');
60+
}
61+
62+
try {
63+
const configContent = fs.readFileSync(configPath, 'utf8');
64+
const config = yaml.load(configContent);
65+
66+
// Validate required fields
67+
if (!config.name) throw new Error('Provider name is required in codeboltprovider.yaml');
68+
if (!config.unique_id) throw new Error('unique_id is required in codeboltprovider.yaml');
69+
if (!config.description) throw new Error('Description is required in codeboltprovider.yaml');
70+
if (!config.version) throw new Error('Version is required in codeboltprovider.yaml');
71+
if (!config.author) throw new Error('Author is required in codeboltprovider.yaml');
72+
73+
return config;
74+
} catch (err) {
75+
throw new Error(`Failed to parse codeboltprovider.yaml: ${err.message}`);
76+
}
77+
};
78+
79+
const publishProvider = async (targetPath) => {
80+
let authToken;
81+
let username;
82+
83+
// Check if the user is logged in
84+
if (!checkUserAuth()) {
85+
console.log(chalk.red('User not authenticated. Please login first.'));
86+
return;
87+
}
88+
89+
try {
90+
const data = getUserData();
91+
authToken = data.jwtToken;
92+
93+
// Get current user's username
94+
try {
95+
const getUsernameResponse = await axios.get(
96+
'https://api.codebolt.ai/api/auth/check-username',
97+
{ headers: { 'Authorization': `Bearer ${authToken}` } }
98+
);
99+
username = getUsernameResponse.data.usersData[0].username;
100+
} catch (err) {
101+
throw new Error(`Failed to get username: ${err.message}`);
102+
}
103+
104+
console.log(chalk.blue('Processing the Provider....'));
105+
106+
const folderPath = targetPath || '.';
107+
const folder = path.resolve(folderPath);
108+
109+
// Read and validate provider configuration
110+
const providerConfig = await readProviderConfig(folderPath);
111+
112+
console.log(chalk.green(`Found provider configuration: ${providerConfig.name}`));
113+
114+
115+
// Run build
116+
try {
117+
await runBuild(folderPath);
118+
console.log(chalk.green('Build completed successfully.'));
119+
} catch (error) {
120+
console.log(chalk.red('Build failed:', error.message || error));
121+
return;
122+
}
123+
124+
// Read .gitignore file and add its contents to the ignore list
125+
const gitignorePath = path.join(folder, '.gitignore');
126+
const ignoreFiles = ['node_modules/**/*', '**/*.zip']; // Base ignore patterns
127+
128+
if (fs.existsSync(gitignorePath)) {
129+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
130+
ignoreFiles.push(...gitignoreContent.split('\n').filter(line => line && !line.startsWith('#')));
131+
}
132+
133+
// Create dist build zip
134+
console.log(chalk.blue('Packaging distribution build...'));
135+
const distZipPath = `${folder}/build.zip`;
136+
137+
await createZipArchive(`${folder}/dist`, distZipPath, ignoreFiles);
138+
console.log(chalk.green('Distribution build packaging done.'));
139+
140+
// Create source code zip
141+
console.log(chalk.blue('Packaging source code...'));
142+
const sourceZipPath = `${folder}/source.zip`;
143+
144+
await createZipArchive(folder, sourceZipPath, ignoreFiles);
145+
console.log(chalk.green('Source code packaging done.'));
146+
147+
// Upload both zip files
148+
console.log(chalk.blue('Uploading distribution build...'));
149+
const distUploadResult = await uploadFile(distZipPath, 'provider', authToken);
150+
const distPublicUrl = distUploadResult.publicUrl;
151+
152+
console.log(chalk.blue('Uploading source code...'));
153+
const sourceUploadResult = await uploadFile(sourceZipPath, 'providerSource', authToken);
154+
const sourcePublicUrl = sourceUploadResult.publicUrl;
155+
156+
console.log(chalk.green('Both packages uploaded successfully.'));
157+
158+
// Clean up zip files
159+
try {
160+
fs.unlinkSync(distZipPath);
161+
fs.unlinkSync(sourceZipPath);
162+
} catch (err) {
163+
console.warn(chalk.yellow(`Warning: Could not delete temp zip files: ${err.message}`));
164+
}
165+
166+
// Submit to API with both zip URLs
167+
const providerData = {
168+
name: providerConfig.name,
169+
unique_id: providerConfig.unique_id,
170+
description: providerConfig.description,
171+
tags: providerConfig.tags ? providerConfig.tags.join(', ') : '',
172+
author: providerConfig.author,
173+
version: providerConfig.version,
174+
zipFilePath: distPublicUrl,
175+
sourceCodeUrl: sourcePublicUrl,
176+
createdByUser: username
177+
};
178+
179+
try {
180+
const providerResponse = await axios.post(
181+
'https://api.codebolt.ai/api/providers/add',
182+
providerData,
183+
{
184+
headers: {
185+
'Authorization': `Bearer ${authToken}`
186+
}
187+
}
188+
);
189+
190+
if (providerResponse.status === 201) {
191+
console.log(chalk.green(providerResponse.data.message));
192+
} else {
193+
console.log(chalk.yellow(providerResponse.data.message));
194+
}
195+
196+
console.log(chalk.blue(`📦 Provider ID: ${providerConfig.unique_id}`));
197+
console.log(chalk.blue(`📝 Description: ${providerConfig.description}`));
198+
console.log(chalk.blue(`🏷️ Tags: ${providerConfig.tags ? providerConfig.tags.join(', ') : 'None'}`));
199+
200+
} catch (err) {
201+
throw new Error(`Failed to add provider: ${err.response?.data?.message || err.message}`);
202+
}
203+
204+
} catch (error) {
205+
console.error(chalk.red('Error:'), error.message || error);
206+
if (error.response) {
207+
console.error(chalk.red('Server response:'), error.response.data);
208+
}
209+
}
210+
};
211+
212+
module.exports = {
213+
publishProvider
214+
};

actions/publishTool.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,6 @@ const publishTool = async (targetPath) => {
203203
console.log(chalk.yellow('Please update the unique_id in your codebolttool.yaml file to create a new tool.'));
204204
return;
205205
}
206-
207-
208206
} catch (err) {
209207
console.warn(chalk.yellow(`Warning: Could not check existing MCP: ${err.message}`));
210208
}

0 commit comments

Comments
 (0)