1- #!/usr/bin/env tsx
1+ /**
2+ * Project Onboarding Script
3+ *
4+ * This script automates the onboarding of multiple open-source projects from a CSV file
5+ * into the CDP platform. It performs the following operations for each project:
6+ *
7+ * 1. Creates a new project in the specified project group (LF OSS Index)
8+ * 2. Sets up GitHub integration for the project's repository
9+ *
10+ * Features:
11+ * - Batch processing of projects from CSV input
12+ * - Dry run mode for testing (processes only the first project)
13+ * - Error handling and detailed logging
14+ * - Console output of failed projects for easy troubleshooting
15+ *
16+ * Usage:
17+ * tsx src/bin/onboard-projects.ts <bearer-token> <csv-file-path> [--dry-run]
18+ *
19+ * CSV Format:
20+ * project name,project slug,repo url
21+ * My Project,my-project,https://github.com/owner/repo
22+ *
23+ * Environment Variables Required:
24+ * CROWD_API_SERVICE_URL - Base URL for the CDP API service
25+ */
226import axios from 'axios'
327import { parse } from 'csv-parse'
428import fs from 'fs'
@@ -20,6 +44,18 @@ interface FailedProjectRow extends ProjectRow {
2044
2145const LF_OSS_INDEX_PROJECT_GROUP_SLUG = 'lf-oss-index'
2246
47+ /**
48+ * Main function to onboard projects from a CSV file
49+ *
50+ * Reads a CSV file containing project information, validates the data,
51+ * and processes each project by creating the project and setting up
52+ * GitHub integration.
53+ *
54+ * @param csvFilePath - Path to the CSV file containing project data
55+ * @param bearerToken - Authentication token for API calls
56+ * @param isDryRun - If true, only processes the first project for testing
57+ * @returns Promise resolving to results summary including success/failure counts and failed projects
58+ */
2359async function onboardProjectsFromCsv (
2460 csvFilePath : string ,
2561 bearerToken : string ,
@@ -136,6 +172,17 @@ async function onboardProjectsFromCsv(
136172 return result
137173}
138174
175+ /**
176+ * Creates a new project in the CDP platform
177+ *
178+ * Makes an API call to create a project within the LF OSS Index project group.
179+ * The project is created as a non-LF project with the specified name and slug.
180+ *
181+ * @param project - Project data containing name, slug, and repository URL
182+ * @param bearerToken - Authentication token for API authorization
183+ * @returns Promise resolving to the segment ID of the created project
184+ * @throws Error if project creation fails or returns invalid response
185+ */
139186async function createProject ( project : ProjectRow , bearerToken : string ) : Promise < string > {
140187 const url = `${ process . env [ 'CROWD_API_SERVICE_URL' ] } /segment/project`
141188
@@ -170,6 +217,46 @@ async function createProject(project: ProjectRow, bearerToken: string): Promise<
170217 }
171218}
172219
220+ /**
221+ * Fetches the GitHub organization or user avatar URL
222+ *
223+ * Makes an API call to GitHub's users endpoint to retrieve the organization's
224+ * or user's avatar URL for use as a logo in the integration settings.
225+ *
226+ * @param owner - GitHub organization or user name
227+ * @param bearerToken - Authentication token for GitHub API calls
228+ * @returns Promise resolving to the avatar URL, or empty string if fetch fails
229+ */
230+ async function fetchGithubOrgLogo ( owner : string , bearerToken : string ) : Promise < string > {
231+ try {
232+ const response = await axios . get ( `https://api.github.com/users/${ owner } ` , {
233+ headers : {
234+ Authorization : `Bearer ${ bearerToken } ` ,
235+ 'Content-Type' : 'application/json' ,
236+ } ,
237+ timeout : 10000 ,
238+ } )
239+
240+ return response . data . avatar_url || ''
241+ } catch ( error ) {
242+ log . warn ( `Failed to fetch GitHub logo for ${ owner } : ${ error . message } ` )
243+ return ''
244+ }
245+ }
246+
247+ /**
248+ * Creates a GitHub integration for the specified project
249+ *
250+ * Sets up GitHub repository integration by parsing the repository URL,
251+ * creating integration settings with repository mapping, and linking
252+ * it to the project segment.
253+ *
254+ * @param project - Project data containing repository URL and metadata
255+ * @param segmentId - The segment ID of the created project to link integration to
256+ * @param bearerToken - Authentication token for API authorization
257+ * @returns Promise that resolves when integration is successfully created
258+ * @throws Error if integration creation fails or returns invalid response
259+ */
173260async function createGithubIntegration (
174261 project : ProjectRow ,
175262 segmentId : string ,
@@ -178,6 +265,9 @@ async function createGithubIntegration(
178265 // Parse GitHub repo URL to extract owner and repo name
179266 const { owner, repo } = parseGithubUrl ( project . repoUrl )
180267
268+ // Fetch organization logo
269+ const orgLogo = await fetchGithubOrgLogo ( owner , bearerToken )
270+
181271 // Create integration
182272 const integrationUrl = `${ process . env [ 'CROWD_API_SERVICE_URL' ] } /github-nango-connect`
183273
@@ -187,7 +277,7 @@ async function createGithubIntegration(
187277 {
188278 name : owner ,
189279 url : project . repoUrl ,
190- logo : '' ,
280+ logo : orgLogo ,
191281 fullSync : false ,
192282 updatedAt : new Date ( ) . toISOString ( ) ,
193283 repos : [
@@ -231,6 +321,20 @@ async function createGithubIntegration(
231321 }
232322}
233323
324+ /**
325+ * Parses a GitHub repository URL to extract owner and repository name
326+ *
327+ * Handles various GitHub URL formats including HTTPS and SSH URLs.
328+ * Normalizes SSH URLs to HTTPS format for consistent parsing.
329+ *
330+ * @param repoUrl - GitHub repository URL (HTTPS or SSH format)
331+ * @returns Object containing the repository owner and name
332+ * @throws Error if URL format is invalid or cannot be parsed
333+ *
334+ * @example
335+ * parseGithubUrl('https://github.com/owner/repo') // { owner: 'owner', repo: 'repo' }
336+ * parseGithubUrl('git@github.com:owner/repo.git') // { owner: 'owner', repo: 'repo' }
337+ */
234338function parseGithubUrl ( repoUrl : string ) : { owner : string ; repo : string } {
235339 try {
236340 // Handle different GitHub URL formats
@@ -253,6 +357,20 @@ function parseGithubUrl(repoUrl: string): { owner: string; repo: string } {
253357 }
254358}
255359
360+ /**
361+ * Main entry point for the project onboarding script
362+ *
363+ * Handles command-line argument parsing, file validation, and orchestrates
364+ * the project onboarding process. Displays results and error information
365+ * to the console upon completion.
366+ *
367+ * Command-line arguments:
368+ * - bearerToken: Authentication token for API calls
369+ * - csvFilePath: Path to CSV file containing project data
370+ * - --dry-run: Optional flag to process only the first project
371+ *
372+ * Exits with code 0 on success, 1 if any projects failed to onboard
373+ */
256374async function main ( ) {
257375 const args = process . argv . slice ( 2 )
258376
0 commit comments