@@ -32,7 +32,7 @@ import {
3232 deleteProjectAnnouncement as dbDeleteProjectAnnouncement ,
3333 updateProjectGithubRepo as dbUpdateProjectGithubRepo ,
3434 getUserGithubOAuthToken as dbGetUserGithubOAuthToken ,
35- getUserGithubInstallation ,
35+ // getUserGithubInstallation, // Not used directly, part of GitHub client
3636} from '@/lib/db' ;
3737import { z } from 'zod' ;
3838import { auth } from '@/lib/authEdge' ;
@@ -565,8 +565,8 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
565565 message : 'Update README.md from FlowUp' ,
566566 content : Buffer . from ( content ) . toString ( 'base64' ) ,
567567 committer : {
568- name : 'FlowUp Bot' ,
569- email : 'bot@flowup.app' ,
568+ name : 'FlowUp Bot' , // Or use the user's GitHub name if available
569+ email : 'bot@flowup.app' , // Or user's email if allowed and consented
570570 } ,
571571 } ;
572572 if ( existingSha ) {
@@ -579,8 +579,6 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
579579 } catch ( error : any ) {
580580 if ( error . status === 404 && ! existingSha ) {
581581 console . log ( `[updateReadmeOnGithub] README.md not found for ${ owner } /${ repo } , creating it.` ) ;
582- // If it was a 404 and we didn't have an existing SHA, it means the file doesn't exist.
583- // We can try creating it without the SHA.
584582 const paramsForCreation = { ...paramsForUpdate } ;
585583 delete paramsForCreation . sha ;
586584 try {
@@ -597,6 +595,7 @@ async function updateReadmeOnGithub(octokit: Octokit, owner: string, repo: strin
597595 }
598596}
599597
598+
600599export async function saveProjectReadmeAction ( prevState : SaveProjectReadmeFormState , formData : FormData ) : Promise < SaveProjectReadmeFormState > {
601600 const session = await auth ( ) ;
602601 if ( ! session ?. user ?. uuid ) {
@@ -625,7 +624,17 @@ export async function saveProjectReadmeAction(prevState: SaveProjectReadmeFormSt
625624 const oauthToken = await dbGetUserGithubOAuthToken ( session . user . uuid ) ;
626625 if ( oauthToken ?. accessToken ) {
627626 const octokit = new Octokit ( { auth : oauthToken . accessToken } ) ;
628- const [ owner , repo ] = updatedProject . githubRepoName . split ( '/' ) ;
627+
628+ const repoParts = updatedProject . githubRepoName . split ( '/' ) ;
629+ if ( repoParts . length !== 2 ) {
630+ console . error ( `[saveProjectReadmeAction] Invalid githubRepoName format: ${ updatedProject . githubRepoName } ` ) ;
631+ return { message : "README saved in FlowUp, but GitHub repo name format is invalid for GitHub update." , project : updatedProject } ;
632+ }
633+ const [ owner , repo ] = repoParts ;
634+ if ( ! owner || ! repo ) {
635+ console . error ( `[saveProjectReadmeAction] Invalid owner or repo after splitting githubRepoName: ${ updatedProject . githubRepoName } ` ) ;
636+ return { message : "README saved in FlowUp, but GitHub owner or repo name is invalid for GitHub update." , project : updatedProject } ;
637+ }
629638
630639 let existingSha : string | null = null ;
631640 try {
@@ -1112,15 +1121,23 @@ export async function fetchUserGithubOAuthTokenAction(): Promise<UserGithubOAuth
11121121 }
11131122}
11141123
1115- function sanitizeRepoName ( name : string ) : string {
1116- let sanitized = name
1117- . trim ( )
1118- . replace ( / \s + / g, '-' )
1119- . replace ( / [ ^ a - z A - Z 0 - 9 _ . - ] / g, '' )
1120- . replace ( / - + / g, '-' )
1121- . replace ( / ^ \. + | \. $ | ^ - + | - + $ / g, '' ) ;
1124+ function sanitizeRepoName ( name : string | null | undefined ) : string {
1125+ if ( ! name ) {
1126+ return '' ;
1127+ }
1128+ const trimmedName = name . trim ( ) ;
1129+ if ( trimmedName === '' ) {
1130+ return '' ;
1131+ }
11221132
1123- return sanitized . substring ( 0 , 100 ) ;
1133+ let sanitized = trimmedName
1134+ . replace ( / \s + / g, '-' ) // Replace spaces with hyphens
1135+ . replace ( / [ ^ a - z A - Z 0 - 9 _ . - ] / g, '' ) // Remove invalid characters
1136+ . replace ( / - + / g, '-' ) // Replace multiple hyphens with a single one
1137+ . replace ( / ^ \. + | \. $ / g, '' ) // Remove leading/trailing dots
1138+ . replace ( / ^ - + | - + $ / g, '' ) ; // Remove leading/trailing hyphens
1139+
1140+ return sanitized . substring ( 0 , 100 ) ; // GitHub repo name max length is 100
11241141}
11251142
11261143
@@ -1140,28 +1157,35 @@ export async function linkProjectToGithubAction(
11401157 }
11411158
11421159 const projectUuid = formData . get ( 'projectUuid' ) as string ;
1143- const flowUpProjectName = formData . get ( 'flowUpProjectName' ) as string ;
1144- const githubRepoNameInput = formData . get ( 'githubRepoName' ) as string ;
1160+ const flowUpProjectNameValue = formData . get ( 'flowUpProjectName' ) ;
1161+ const githubRepoNameValue = formData . get ( 'githubRepoName' ) ;
11451162 const useDefaultRepoName = formData . get ( 'useDefaultRepoName' ) === 'true' ;
11461163
1164+ let nameForRepoCreation : string ;
11471165
1148- if ( ! projectUuid || ! flowUpProjectName ) {
1149- return { error : "Project UUID and FlowUp Project Name are required." } ;
1166+ if ( useDefaultRepoName ) {
1167+ if ( typeof flowUpProjectNameValue === 'string' && flowUpProjectNameValue . trim ( ) !== '' ) {
1168+ nameForRepoCreation = `FlowUp-${ flowUpProjectNameValue } ` ;
1169+ } else {
1170+ return { error : "FlowUp Project Name is required to generate the default repository name." } ;
1171+ }
1172+ } else {
1173+ if ( typeof githubRepoNameValue === 'string' && githubRepoNameValue . trim ( ) !== '' ) {
1174+ nameForRepoCreation = githubRepoNameValue ;
1175+ } else {
1176+ return { error : "Custom repository name cannot be empty." } ;
1177+ }
1178+ }
1179+
1180+ const repoSlug = sanitizeRepoName ( nameForRepoCreation ) ;
1181+ if ( ! repoSlug ) {
1182+ return { error : "Resulting repository name is invalid after sanitization. Please provide a valid name." } ;
11501183 }
11511184
11521185 const projectFromDb = await dbGetProjectByUuid ( projectUuid ) ;
11531186 if ( ! projectFromDb ) {
11541187 return { error : "FlowUp project not found." } ;
11551188 }
1156-
1157- const repoSlug = useDefaultRepoName
1158- ? sanitizeRepoName ( `FlowUp-${ flowUpProjectName } ` )
1159- : sanitizeRepoName ( githubRepoNameInput ) ;
1160-
1161- if ( ! repoSlug ) {
1162- return { error : "Invalid repository name. Please provide a valid name or use the default." } ;
1163- }
1164-
11651189 const repoIsPrivate = projectFromDb . isPrivate !== undefined ? projectFromDb . isPrivate : true ;
11661190
11671191
@@ -1185,28 +1209,35 @@ export async function linkProjectToGithubAction(
11851209 createdRepo = await octokit . rest . repos . createForAuthenticatedUser ( {
11861210 name : repoSlug ,
11871211 private : repoIsPrivate ,
1188- description : `Repository for FlowUp project: ${ flowUpProjectName } ` ,
1212+ description : `Repository for FlowUp project: ${ projectFromDb . name } ` ,
11891213 auto_init : true ,
11901214 } ) ;
11911215 console . log ( `Successfully created repository: ${ createdRepo . data . html_url } ` ) ;
11921216
11931217 if ( projectFromDb . readmeContent && projectFromDb . readmeContent . trim ( ) !== '' ) {
1194- const { data : readmeData } = await octokit . rest . repos . getContent ( {
1195- owner : createdRepo . data . owner . login ,
1196- repo : createdRepo . data . name ,
1197- path : 'README.md' ,
1198- } ) ;
1199- let existingReadmeSha : string | undefined = undefined ;
1200- if ( 'sha' in readmeData && readmeData . type === 'file' ) {
1201- existingReadmeSha = readmeData . sha ;
1218+ // Fetch existing README's SHA to avoid errors if it was created by auto_init
1219+ let existingReadmeSha : string | undefined = undefined ;
1220+ try {
1221+ const { data : readmeData } = await octokit . rest . repos . getContent ( {
1222+ owner : createdRepo . data . owner . login ,
1223+ repo : createdRepo . data . name ,
1224+ path : 'README.md' ,
1225+ } ) ;
1226+ if ( 'sha' in readmeData && readmeData . type === 'file' ) { // Check if readmeData is a file object
1227+ existingReadmeSha = readmeData . sha ;
1228+ }
1229+ } catch ( getContentError : any ) {
1230+ if ( getContentError . status !== 404 ) { // Log error if not a "file not found"
1231+ console . warn ( `[linkProjectToGithubAction] Could not fetch existing README SHA for ${ createdRepo . data . owner . login } /${ createdRepo . data . name } :` , getContentError . message ) ;
1232+ }
12021233 }
12031234 await updateReadmeOnGithub ( octokit , createdRepo . data . owner . login , createdRepo . data . name , projectFromDb . readmeContent , existingReadmeSha ) ;
12041235 }
12051236
12061237 } catch ( apiError : any ) {
12071238 console . error ( `GitHub API error creating repository: ${ apiError . status } ${ apiError . message } ` , apiError . response ?. data ) ;
12081239 const errorMessage = apiError . response ?. data ?. message || apiError . message || 'Unknown GitHub API error.' ;
1209- if ( apiError . status === 422 ) {
1240+ if ( apiError . status === 422 ) { // Unprocessable Entity (e.g., repo already exists)
12101241 return { error : `Failed to create GitHub repository '${ repoSlug } '. It might already exist or there's a naming conflict. GitHub's message: ${ errorMessage } ` } ;
12111242 }
12121243 return { error : `GitHub API Error (${ apiError . status || 'unknown' } ): ${ errorMessage } - ${ apiError . documentation_url || '' } ` } ;
@@ -1215,7 +1246,7 @@ export async function linkProjectToGithubAction(
12151246 const repoUrl = createdRepo . data . html_url ;
12161247 const actualRepoName = createdRepo . data . full_name ;
12171248
1218- const updatedProject = await dbUpdateProjectGithubRepo ( projectUuid , repoUrl , actualRepoName , null ) ;
1249+ const updatedProject = await dbUpdateProjectGithubRepo ( projectUuid , repoUrl , actualRepoName ) ;
12191250
12201251 if ( ! updatedProject ) {
12211252 return { error : "Failed to save GitHub repository details to FlowUp project after creation on GitHub." } ;
@@ -1229,9 +1260,13 @@ export async function linkProjectToGithubAction(
12291260 }
12301261}
12311262
1263+ /*
1264+ // This function might be needed if we switch back to GitHub App installation flow for repo creation
1265+ // For now, with OAuth flow, it's not directly used for creating repos but good for fetching installation details
1266+ // if we need to interact with the app installation itself.
12321267export async function fetchUserGithubAccessDetailsAction(userUuid: string): Promise<{
12331268 oauthToken: UserGithubOAuthToken | null;
1234- installation : UserGithubInstallation | null ;
1269+ installation: UserGithubInstallation | null; // Placeholder, not fully implemented for OAuth flow
12351270}> {
12361271 const session = await auth();
12371272 if (!session?.user?.uuid || session.user.uuid !== userUuid) {
@@ -1240,10 +1275,13 @@ export async function fetchUserGithubAccessDetailsAction(userUuid: string): Prom
12401275 }
12411276 try {
12421277 const oauthToken = await dbGetUserGithubOAuthToken(userUuid);
1243- const installation = await getUserGithubInstallation ( userUuid ) ;
1244- return { oauthToken, installation } ;
1278+ // const installation = await getUserGithubInstallation(userUuid); // This is for GitHub App installs
1279+ return { oauthToken, installation: null }; // Return null for installation in pure OAuth flow
12451280 } catch (error) {
12461281 console.error("[fetchUserGithubAccessDetailsAction] Error fetching GitHub access details:", error);
12471282 return { oauthToken: null, installation: null };
12481283 }
12491284}
1285+ */
1286+
1287+ `` `
0 commit comments