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+ } ;
0 commit comments