feat(app): add functionality to generate commit messages with co-authors

This commit is contained in:
Fatih Kadir Akın
2025-12-13 20:37:15 +03:00
parent fdad60ba98
commit 1fd0ffd9fd
2 changed files with 77 additions and 29 deletions
+50 -20
View File
@@ -97,6 +97,34 @@ if not new_prompts and not updated_prompts:
import sys
sys.exit(2) # Exit code 2 = no changes
else:
# Helper function to parse contributors (supports "user1,user2,user3" format)
def parse_contributors(contributor_field):
"""Parse contributor field, returns (primary_author, co_authors_list)"""
if not contributor_field:
return 'anonymous', []
# Split by comma and clean up
contributors = [c.strip() for c in contributor_field.split(',') if c.strip()]
if not contributors:
return 'anonymous', []
primary = contributors[0]
co_authors = contributors[1:] if len(contributors) > 1 else []
return primary, co_authors
def build_commit_message(action, act, co_authors):
"""Build commit message with optional co-author trailers"""
msg = f'{action} prompt: {act}'
if co_authors:
msg += '\n\n'
for co_author in co_authors:
co_email = f"{co_author}@users.noreply.github.com"
msg += f'Co-authored-by: {co_author} <{co_email}>\n'
return msg
# Process updates first (rewrite entire file with updates applied)
if updated_prompts:
print("\nApplying updates to existing prompts...")
@@ -118,42 +146,41 @@ else:
# Commit updates
for i, (remote_row, local_row) in enumerate(updated_prompts, 1):
contributor = remote_row.get('contributor', '').strip()
contributor_field = remote_row.get('contributor', '').strip()
act = remote_row.get('act', 'Unknown')
if not contributor:
contributor = 'anonymous'
email = f"{contributor}@users.noreply.github.com"
primary_author, co_authors = parse_contributors(contributor_field)
email = f"{primary_author}@users.noreply.github.com"
subprocess.run(['git', 'add', csv_file], check=True)
env = os.environ.copy()
env['GIT_AUTHOR_NAME'] = contributor
env['GIT_AUTHOR_NAME'] = primary_author
env['GIT_AUTHOR_EMAIL'] = email
env['GIT_COMMITTER_NAME'] = contributor
env['GIT_COMMITTER_NAME'] = primary_author
env['GIT_COMMITTER_EMAIL'] = email
commit_msg = build_commit_message('Update', act, co_authors)
subprocess.run([
'git', 'commit',
'-m', f'Update prompt: {act}',
f'--author={contributor} <{email}>'
'-m', commit_msg,
f'--author={primary_author} <{email}>'
], env=env, check=True)
print(f"[UPDATE {i}/{len(updated_prompts)}] {contributor}: {act}")
co_authors_str = f" (+ {', '.join(co_authors)})" if co_authors else ""
print(f"[UPDATE {i}/{len(updated_prompts)}] {primary_author}{co_authors_str}: {act}")
# Process new prompts
if new_prompts:
print("\nCreating commits for new prompts...")
for i, row in enumerate(new_prompts, 1):
contributor = row.get('contributor', '').strip()
contributor_field = row.get('contributor', '').strip()
act = row.get('act', 'Unknown')
if not contributor:
contributor = 'anonymous'
email = f"{contributor}@users.noreply.github.com"
primary_author, co_authors = parse_contributors(contributor_field)
email = f"{primary_author}@users.noreply.github.com"
# Append this row to the CSV
with open(csv_file, 'a', newline='') as f:
@@ -164,18 +191,21 @@ else:
subprocess.run(['git', 'add', csv_file], check=True)
env = os.environ.copy()
env['GIT_AUTHOR_NAME'] = contributor
env['GIT_AUTHOR_NAME'] = primary_author
env['GIT_AUTHOR_EMAIL'] = email
env['GIT_COMMITTER_NAME'] = contributor
env['GIT_COMMITTER_NAME'] = primary_author
env['GIT_COMMITTER_EMAIL'] = email
commit_msg = build_commit_message('Add', act, co_authors)
subprocess.run([
'git', 'commit',
'-m', f'Add prompt: {act}',
f'--author={contributor} <{email}>'
'-m', commit_msg,
f'--author={primary_author} <{email}>'
], env=env, check=True)
print(f"[NEW {i}/{len(new_prompts)}] {contributor}: {act}")
co_authors_str = f" (+ {', '.join(co_authors)})" if co_authors else ""
print(f"[NEW {i}/{len(new_prompts)}] {primary_author}{co_authors_str}: {act}")
print(f"\nDone! Created {len(new_prompts)} new commits, {len(updated_prompts)} update commits.")
+27 -9
View File
@@ -14,6 +14,15 @@ function escapeCSVField(field: string): string {
return field;
}
function getUserIdentifier(user: { email: string; username: string; githubUsername: string | null }): string {
// Determine contributor identifier (immutable to prevent impersonation):
// 1. githubUsername if set (GitHub OAuth users)
// 2. username if email ends with @unclaimed.prompts.chat (imported GitHub contributors)
// 3. email for others (Google, credentials)
const isUnclaimedAccount = user.email.endsWith('@unclaimed.prompts.chat');
return user.githubUsername || (isUnclaimedAccount ? user.username : user.email);
}
export async function GET() {
try {
const prompts = await db.prompt.findMany({
@@ -34,6 +43,13 @@ export async function GET() {
githubUsername: true,
},
},
contributors: {
select: {
email: true,
username: true,
githubUsername: true,
},
},
},
orderBy: { createdAt: "asc" },
});
@@ -44,16 +60,18 @@ export async function GET() {
const promptContent = escapeCSVField(prompt.content);
const forDevs = prompt.category?.slug === "coding" ? "TRUE" : "FALSE";
const type = prompt.type;
// Determine contributor identifier (immutable to prevent impersonation):
// 1. githubUsername if set (GitHub OAuth users)
// 2. username if email ends with @unclaimed.prompts.chat (imported GitHub contributors)
// 3. email for others (Google, credentials)
const isUnclaimedAccount = prompt.author.email.endsWith('@unclaimed.prompts.chat');
const contributor = escapeCSVField(
prompt.author.githubUsername || (isUnclaimedAccount ? prompt.author.username : prompt.author.email)
);
return [act, promptContent, forDevs, type, contributor].join(",");
// Build contributor list: author first, then co-contributors
// Format: "@author,@contributor1,@contributor2" or just "@author"
const authorId = getUserIdentifier(prompt.author);
const contributorIds = prompt.contributors
.map((c: { email: string; username: string; githubUsername: string | null }) => getUserIdentifier(c))
.filter((id: string) => id !== authorId); // Exclude author from contributors list
const allContributors = [authorId, ...contributorIds];
const contributorField = escapeCSVField(allContributors.join(","));
return [act, promptContent, forDevs, type, contributorField].join(",");
});
const csvContent = [headers.join(","), ...rows].join("\n");