-
Notifications
You must be signed in to change notification settings - Fork 2
386 lines (374 loc) · 17.8 KB
/
welcome.yml
File metadata and controls
386 lines (374 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
name: Guest Welcome Bot
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to test with"
required: true
type: number
jobs:
generate-prep:
if: ${{ github.event.label.name == 'scheduled' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install dependencies
run: |
npm install -g @github/copilot
npm install @github/copilot-sdk puppeteer
- name: Get issue data
id: get-issue
uses: actions/github-script@v7
with:
script: |
let issueNumber;
let issue;
if (context.eventName === 'workflow_dispatch') {
issueNumber = ${{ github.event.inputs.issue_number || 0 }};
const response = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber
});
issue = response.data;
} else {
issue = context.payload.issue;
issueNumber = issue.number;
}
core.setOutput('issue_number', issueNumber);
core.setOutput('issue_body', issue.body || '');
core.setOutput('issue_url', issue.html_url);
- name: Parse issue data
id: parse
env:
ISSUE_BODY: ${{ steps.get-issue.outputs.issue_body }}
run: |
cat > parse.mjs << 'ENDSCRIPT'
import { appendFileSync } from 'fs';
const body = process.env.ISSUE_BODY || '';
const nameMatch = body.match(/### Name\s*\n\s*([^\n]+)/i);
const handleMatch = body.match(/### GitHub Handle\s*\n\s*@?([^\n\s]+)/i);
const projectMatch = body.match(/### Project Name\s*\n\s*([^\n]+)/i);
const repoMatch = body.match(/### Project Repo Link\s*\n\s*(https:\/\/github\.com\/[^\s]+)/i);
const descMatch = body.match(/### Tell us about yourself\s*\n\s*([\s\S]*?)(?=\n###|$)/i);
let guestName = nameMatch ? nameMatch[1].trim() : 'Guest';
let handle = handleMatch ? handleMatch[1].trim().replace(/^@/, '') : '';
const projectName = projectMatch ? projectMatch[1].trim() : '';
const projectRepo = repoMatch ? repoMatch[1].trim() : '';
const guestBackground = descMatch ? descMatch[1].trim() : '';
if (!handle && projectRepo) {
const repoUserMatch = projectRepo.match(/github\.com\/([^\/]+)/);
if (repoUserMatch) {
handle = repoUserMatch[1];
console.log('Extracted handle from repo URL:', handle);
}
}
console.log('=== PARSED ISSUE DATA ===');
console.log('Guest Name:', guestName);
console.log('Handle:', handle);
console.log('Project:', projectName);
console.log('Repo:', projectRepo);
const outputFile = process.env.GITHUB_OUTPUT;
appendFileSync(outputFile, 'guest_name=' + guestName + '\n');
appendFileSync(outputFile, 'handle=' + handle + '\n');
appendFileSync(outputFile, 'project_name=' + projectName + '\n');
appendFileSync(outputFile, 'project_repo=' + projectRepo + '\n');
const bg = guestBackground.replace(/\n/g, ' ').substring(0, 500);
appendFileSync(outputFile, 'guest_background=' + bg + '\n');
console.log('Outputs written successfully');
ENDSCRIPT
node parse.mjs
- name: Determine generation mode
id: mode
run: |
echo "mode=${{ case(
steps.parse.outputs.handle != '' && steps.parse.outputs.project_repo != '', 'full-thumbnail',
steps.parse.outputs.guest_name != '' && steps.parse.outputs.handle == '', 'name-only',
'skip'
) }}" >> $GITHUB_OUTPUT
- name: Generate welcome message with Copilot
id: personalize
env:
GH_TOKEN: ${{ secrets.COPILOT_PAT }}
GUEST_NAME: ${{ steps.parse.outputs.guest_name }}
HANDLE: ${{ steps.parse.outputs.handle }}
PROJECT_NAME: ${{ steps.parse.outputs.project_name }}
PROJECT_REPO: ${{ steps.parse.outputs.project_repo }}
GUEST_BACKGROUND: ${{ steps.parse.outputs.guest_background }}
run: |
cat > copilot.mjs << 'ENDSCRIPT'
import { appendFileSync } from 'fs';
const guestName = process.env.GUEST_NAME || 'Guest';
const projectName = process.env.PROJECT_NAME || '';
const projectRepo = process.env.PROJECT_REPO || '';
const guestBackground = process.env. GUEST_BACKGROUND || '';
console.log('=== GENERATING WELCOME MESSAGE ===');
console.log('Guest:', guestName);
console.log('Project:', projectName);
console.log('Background:', guestBackground);
let personalizedMessage = '';
const promptLines = [
'You are writing on behalf of the Open Source Friday team a group at GitHub who host a weekly livestream featuring open source maintainers.',
'',
'The team vibe: Genuinely curious about open source, supportive and encouraging to guests, technically-minded but approachable, excited without being over-the-top.',
'',
'Guest details:',
'- Name: ' + guestName,
'- Project: ' + (projectName || 'their project'),
'- Repo: ' + projectRepo,
'- About them (in their own words): ' + guestBackground,
'',
'Write a 2-3 sentence welcome message that:',
'1. Greets them by name',
'2. Matches their energy - if they are playful, be playful back; if they are formal, be warm but professional',
'3. References something specific they said about themselves or their project',
'4. Shows genuine excitement about having them on the stream, and encourages them to share their story and demo with the audience',
'',
'Sign off as "The Open Source Friday Team" but do not include a formal greeting like "Dear" or "Hi".',
'',
'Keep it natural and conversational. No emojis. No markdown. Plain text only.'
];
try {
const { CopilotClient } = await import('@github/copilot-sdk');
console. log('Initializing Copilot SDK...');
const client = new CopilotClient();
await client.start();
console.log('Creating session.. .');
const session = await client.createSession();
const prompt = promptLines.join('\n');
console.log('Sending prompt to Copilot...');
const response = await session. sendAndWait({ prompt: prompt });
if (response && response.data && response.data. content) {
personalizedMessage = response.data.content;
console.log('Received personalized message from Copilot');
}
await session. destroy();
await client.stop();
} catch (error) {
console.error('Copilot SDK error:', error.message);
console.error('Stack:', error.stack);
}
if (!personalizedMessage) {
console.log('Using fallback message');
if (projectName) {
personalizedMessage = guestName + ', we are excited to have you on Open Source Friday! ' + projectName + ' sounds like a fantastic project, and we cannot wait to hear more about your journey building it.\n\nThe Open Source Friday Team';
} else {
personalizedMessage = guestName + ', we are thrilled to have you joining us on Open Source Friday. Our audience loves meeting new voices in open source, and we know this is going to be a great session.\n\nThe Open Source Friday Team';
}
}
const outputFile = process.env. GITHUB_OUTPUT;
appendFileSync(outputFile, 'personalized_message<<EOFMSG\n' + personalizedMessage + '\nEOFMSG\n');
console. log('Welcome message generated successfully');
ENDSCRIPT
node copilot.mjs || true
- name: Generate thumbnail
id: thumbnail
if: ${{ steps.mode.outputs.mode == 'full-thumbnail' }}
env:
GUEST_NAME: ${{ steps.parse.outputs.guest_name }}
HANDLE: ${{ steps.parse.outputs.handle }}
PROJECT_REPO: ${{ steps.parse.outputs.project_repo }}
PROJECT_NAME: ${{ steps.parse.outputs.project_name }}
run: |
cat > thumbnail.mjs << 'ENDSCRIPT'
import puppeteer from 'puppeteer';
import { appendFileSync, writeFileSync } from 'fs';
const guestName = process.env.GUEST_NAME || 'Guest';
let handle = process.env.HANDLE || '';
const projectRepo = process.env.PROJECT_REPO || '';
const projectName = process.env.PROJECT_NAME || '';
console.log('=== THUMBNAIL GENERATION ===');
console.log('Guest:', guestName);
console.log('Handle:', handle);
console.log('Project:', projectName);
console.log('Repo:', projectRepo);
if (!handle && projectRepo) {
const repoMatch = projectRepo.match(/github\.com\/([^\/]+)/);
if (repoMatch) {
handle = repoMatch[1];
console.log('Extracted handle from repo URL:', handle);
}
}
if (!handle) {
console.log('No GitHub handle available - skipping thumbnail generation');
appendFileSync(process.env.GITHUB_OUTPUT, 'thumbnail_generated=false\n');
process.exit(0);
}
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1400, height: 900 });
page.on('console', function(msg) {
if (msg.type() === 'error') {
console.log('PAGE ERROR:', msg.text());
}
});
console.log('Loading thumbnail generator...');
await page.goto('https://andreagriffiths11.github.io/thumbnail-gen/', {
waitUntil: 'networkidle0',
timeout: 60000
});
console.log('Page loaded successfully');
console.log('Filling guest name:', guestName);
await page.evaluate(function() { document.getElementById('guestName').value = ''; });
await page.type('#guestName', guestName);
console.log('Filling username:', handle);
await page.evaluate(function() { document.getElementById('username').value = ''; });
await page.type('#username', handle);
if (projectRepo) {
console.log('Filling repo:', projectRepo);
await page.evaluate(function() { document.getElementById('repo').value = ''; });
await page.type('#repo', projectRepo);
}
if (projectName) {
const hasProjectName = await page.$('#projectName');
if (hasProjectName) {
console.log('Filling project name:', projectName);
await page.evaluate(function() { document.getElementById('projectName').value = ''; });
await page.type('#projectName', projectName);
}
}
console.log('Clicking generate button...');
await page.click('#generateBtn');
console.log('Waiting for thumbnail generation...');
let attempts = 0;
const maxAttempts = 30;
let isReady = false;
while (attempts < maxAttempts && !isReady) {
await new Promise(function(resolve) { setTimeout(resolve, 2000); });
const status = await page.evaluate(function() {
const downloadBtn = document.getElementById('downloadBtn');
const banner = document.getElementById('banner');
return {
downloadDisabled: downloadBtn ? downloadBtn.disabled : true,
bannerText: banner ? banner.textContent : '',
bannerClass: banner ? banner.className : ''
};
});
console.log('Attempt ' + (attempts + 1) + '/' + maxAttempts + ': Download disabled=' + status.downloadDisabled);
if (!status.downloadDisabled) {
isReady = true;
console.log('Thumbnail generation complete!');
} else if (status.bannerClass.includes('error')) {
throw new Error('Thumbnail generation failed: ' + status.bannerText);
}
attempts++;
}
if (!isReady) {
throw new Error('Thumbnail generation timed out');
}
await new Promise(function(resolve) { setTimeout(resolve, 1000); });
console.log('Extracting canvas data...');
const thumbnailData = await page.evaluate(function() {
const canvas = document.getElementById('preview');
return canvas.toDataURL('image/png');
});
const base64Data = thumbnailData.replace(/^data:image\/png;base64,/, '');
writeFileSync('thumbnail.png', Buffer.from(base64Data, 'base64'));
console.log('Thumbnail saved successfully!');
appendFileSync(process.env.GITHUB_OUTPUT, 'thumbnail_generated=true\n');
} catch (error) {
console.error('Error generating thumbnail:', error.message);
appendFileSync(process.env.GITHUB_OUTPUT, 'thumbnail_generated=false\n');
} finally {
await browser.close();
}
ENDSCRIPT
node thumbnail.mjs
- name: Upload thumbnail artifact
if: steps.thumbnail.outputs.thumbnail_generated == 'true'
uses: actions/upload-artifact@v4
with:
name: thumbnail-issue-${{ steps.get-issue.outputs.issue_number }}
path: thumbnail.png
retention-days: 90
- name: Post welcome comment
uses: actions/github-script@v7
env:
PERSONALIZED_MESSAGE: ${{ steps.personalize.outputs.personalized_message }}
GUEST_NAME: ${{ steps.parse.outputs.guest_name }}
HANDLE: ${{ steps.parse.outputs.handle }}
ISSUE_NUMBER: ${{ steps.get-issue.outputs.issue_number }}
THUMBNAIL_GENERATED: ${{ steps.thumbnail.outputs.thumbnail_generated }}
RUN_ID: ${{ github.run_id }}
with:
script: |
const personalizedMessage = process.env.PERSONALIZED_MESSAGE;
const guestName = process.env.GUEST_NAME;
const handle = process.env.HANDLE;
const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);
const thumbnailGenerated = process.env.THUMBNAIL_GENERATED === 'true';
const runId = process.env.RUN_ID;
const repoUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo;
const guideUrl = repoUrl + '/blob/main/admin/approved-guest.md';
const thumbnailToolUrl = 'https://andreagriffiths11.github.io/thumbnail-gen/';
const artifactUrl = repoUrl + '/actions/runs/' + runId;
const mention = handle ? '@' + handle : guestName;
let commentParts = [
'## Welcome to Open Source Friday! 🎉',
'',
personalizedMessage,
'',
'---',
''
];
if (thumbnailGenerated) {
commentParts = commentParts.concat([
'### 🖼️ Your Promotional Thumbnail',
'',
'✅ Your custom thumbnail has been generated!',
'',
'**To download it:**',
'1. [Click here to view the workflow run](' + artifactUrl + ')',
'2. Scroll down to **Artifacts**',
'3. Download `thumbnail-issue-' + issueNumber + '`',
'',
'Use this thumbnail to promote your upcoming stream on social media!',
'',
'> 💡 Want to customize it? Use our [thumbnail generator](' + thumbnailToolUrl + ').',
'',
'---',
''
]);
} else {
commentParts = commentParts.concat([
'### 🖼️ Create Your Promotional Thumbnail',
'',
'Generate a custom thumbnail for your stream using our [thumbnail generator](' + thumbnailToolUrl + ').',
'',
'---',
''
]);
}
commentParts = commentParts.concat([
'### ✅ Quick Prep Checklist',
'',
'- [ ] Stream starts at **1:00 PM ET** on your scheduled Friday',
'- [ ] Please join at **12:45 PM ET** for prep and tech checks',
'- [ ] Have your demo ready (live demos are our audience favorite!)',
'- [ ] Share your thumbnail on social media to promote the stream',
'',
'📖 For full preparation guidelines, see our [complete guest guide](' + guideUrl + ').',
'',
'Looking forward to your session, ' + mention + '!',
'',
'— The Open Source Friday Team'
]);
const comment = commentParts.join('\n');
await github.rest.issues.createComment({
issue_number: issueNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
console.log('Comment posted successfully!');