From 89f01943c76b11ac4d5c68a3f2a83e9771fb86ac Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 3 Sep 2025 14:43:46 -0700 Subject: [PATCH 1/2] Script take 1 --- scripts/README-create-event-file.md | 198 +++++++++++ scripts/create-event-file.js | 495 ++++++++++++++++++++++++++++ 2 files changed, 693 insertions(+) create mode 100644 scripts/README-create-event-file.md create mode 100644 scripts/create-event-file.js diff --git a/scripts/README-create-event-file.md b/scripts/README-create-event-file.md new file mode 100644 index 000000000000..0c2d1f2c69d0 --- /dev/null +++ b/scripts/README-create-event-file.md @@ -0,0 +1,198 @@ +# Event/Workshop File Creator + +This script helps you create properly formatted workshop and event files for the Pulumi website with built-in linting validation. + +## How to Run the Script + +### Step 1: Open Terminal/Command Prompt +- **On Mac**: Press `Cmd + Space`, type "Terminal", and press Enter +- **On Windows**: Press `Windows + R`, type "cmd", and press Enter +- **On Linux**: Press `Ctrl + Alt + T` + +### Step 2: Get the Docs Repository + +#### If you already have the docs repo: +Navigate to the folder where your docs repo is: +```bash +cd /Users/pulumipus/docs +``` +*(Replace `/Users/pulumipus/docs` with the actual path to your project folder)* + +#### If you don't have the docs repo yet: +Clone the repository first: +```bash +git clone https://github.com/pulumi/docs.git +cd docs +``` +*(This will download the entire docs repository to your computer)* + +### Step 3: Run the Script +Type this command and press Enter: +```bash +node scripts/create-event-file.js +``` + +### What Happens Next +The script will start and ask you questions. Just type your answers and press Enter after each one. The script will guide you through everything! + +## Features + +### āœ… Built-in Linting Validation +- **Title Length**: Automatically validates that titles are ≤ 60 characters +- **Meta Description Length**: Automatically validates that meta descriptions are ≤ 160 characters +- **Real-time Feedback**: Shows character counts and validation status as you type + +### šŸŽÆ Two File Types + +#### Workshop Files +- Interactive prompts for all required fields +- Learning objectives collection +- Presenter information +- Tags and categorization +- HubSpot/Salesforce form integration +- Automatic date/time formatting + +#### Event Files +- External event support +- Location information +- Registration URL handling +- Simplified structure for external events + +### šŸ“‹ What the Script Prompts For + +**Common Fields:** +- Title (with length validation) +- Meta description (with length validation) +- Full description +- Date, time, timezone, duration +- Location (for events) + +**Workshop-Specific:** +- Learning objectives (multiple) +- Presenter name and role +- Tags (level, topics, languages, clouds) +- HubSpot Form ID +- Salesforce Campaign ID + +**Event-Specific:** +- External registration URL + +### šŸŽØ Automatic Features + +- **Slug Generation**: Creates URL-friendly slugs from titles +- **Date Formatting**: Converts dates to proper ISO format with timezone +- **File Structure**: Creates proper folder structure and file naming +- **YAML Formatting**: Generates properly formatted YAML frontmatter + +### šŸ“ Output + +The script creates: +- A new folder in `content/events/[slug]/` +- An `index.md` file with all the proper YAML frontmatter +- Proper file structure matching existing patterns + +### šŸ”„ Git Integration (Optional) + +After creating the file, the script can automatically: +- Create a new Git branch with a descriptive name +- Commit the new file with a proper commit message +- Push the branch to the remote repository +- Generate a link to create a Pull Request on GitHub + +**Example Git workflow:** +``` +šŸ”„ Would you like to commit this to Git and create a PR? (y/n): y + +šŸ”„ Setting up Git... +šŸ“ Creating branch: add-workshop-my-amazing-workshop +šŸ“ Adding file: content/events/my-amazing-workshop/index.md +šŸ’¾ Committing changes... +šŸš€ Pushing branch to remote... + +āœ… Git operations completed successfully! +🌿 Branch: add-workshop-my-amazing-workshop +šŸ“ Commit: Add workshop: My Amazing Workshop + +šŸ”— Creating Pull Request... + +šŸŽ‰ Pull Request ready to create! +šŸ”— Click here to create the PR: https://github.com/owner/repo/compare/add-workshop-my-amazing-workshop?expand=1 +``` + +### šŸ”§ Example Usage + +Here's what you'll see when you run the script: + +``` +$ node scripts/create-event-file.js + +šŸš€ Pulumi Event/Workshop File Creator + +What would you like to create? (workshop/event): workshop + +šŸŽÆ Creating a Workshop File + +šŸ“‹ Linting Requirements: + • Title must be ≤ 60 characters + • Meta description must be ≤ 160 characters + +šŸ“ Workshop Title: My Amazing Workshop +āœ… Title is 20 characters (within 60 limit) + +šŸ“„ Meta Description: Learn amazing things in this workshop +āœ… Meta description is 45 characters (within 160 limit) + +šŸ“– Full Description: This workshop will teach you... +... +``` + +**Just type your answers and press Enter!** The script will guide you through each step. + +## Troubleshooting + +### "node: command not found" Error +If you see this error, you need to install Node.js: +1. Go to https://nodejs.org/ +2. Download and install the LTS version +3. Restart your terminal/command prompt +4. Try running the script again + +### "No such file or directory" Error +Make sure you're in the right folder: +1. Check that you're in the project folder (the one containing the `scripts` folder) +2. You can type `ls` (Mac/Linux) or `dir` (Windows) to see what's in your current folder +3. You should see a `scripts` folder in the list + +### Script Stops or Freezes +- Press `Ctrl + C` to stop the script +- Make sure you're answering each question and pressing Enter +- If you need to start over, just run the script again + +### Git Issues + +#### "Not a git repository" Error +If you see this error, you need to initialize Git or navigate to the right folder: +1. Make sure you're in the project folder (the one with `.git` folder) +2. If there's no `.git` folder, run: `git init` + +#### "Authentication failed" Error +If Git push fails due to authentication: +1. Make sure you're logged into GitHub on your computer +2. You may need to set up a Personal Access Token +3. The script will show manual commit instructions if Git fails + +#### "Branch already exists" Error +If the branch name already exists: +1. The script will show an error message +2. You can manually create a branch with a different name +3. Or delete the existing branch if it's not needed + +### 🚨 Linting Prevention + +The script actively prevents linting errors by: +- Validating title length in real-time +- Validating meta description length in real-time +- Providing character counts and feedback +- Requiring corrections before proceeding + +This ensures your files will pass the linter on first creation! diff --git a/scripts/create-event-file.js b/scripts/create-event-file.js new file mode 100644 index 000000000000..012b3e873626 --- /dev/null +++ b/scripts/create-event-file.js @@ -0,0 +1,495 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const { execSync } = require('child_process'); + +// Create readline interface +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// Helper function to prompt for input +function askQuestion(question) { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +// Validation functions +function validateTitle(title) { + if (title.length > 60) { + return `āŒ Title is ${title.length} characters (max 60). Please shorten it.`; + } + return `āœ… Title is ${title.length} characters (within 60 limit)`; +} + +function validateMetaDesc(metaDesc) { + if (metaDesc.length > 160) { + return `āŒ Meta description is ${metaDesc.length} characters (max 160). Please shorten it.`; + } + return `āœ… Meta description is ${metaDesc.length} characters (within 160 limit)`; +} + +// Helper function to create slug from title +function createSlug(title) { + return title + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .trim('-'); +} + +// Helper function to format date for sortable_date +function formatSortableDate(dateStr, timeStr, timezone) { + const date = new Date(`${dateStr}T${timeStr}`); + const offset = timezone === 'EDT' ? '-04:00' : + timezone === 'EST' ? '-05:00' : + timezone === 'PDT' ? '-07:00' : + timezone === 'PST' ? '-08:00' : '-00:00'; + return date.toISOString().replace('Z', offset); +} + +// Git functions +function runGitCommand(command, options = {}) { + try { + return execSync(command, { + encoding: 'utf8', + stdio: options.silent ? 'pipe' : 'inherit', + ...options + }); + } catch (error) { + if (!options.silent) { + console.error(`āŒ Git command failed: ${command}`); + console.error(error.message); + } + throw error; + } +} + +function checkGitStatus() { + try { + const status = runGitCommand('git status --porcelain', { silent: true }); + return status.trim(); + } catch (error) { + return null; + } +} + +function createBranchAndCommit(filePath, title, fileType) { + const branchName = `add-${fileType}-${createSlug(title).replace(/-/g, '-')}`; + const commitMessage = `Add ${fileType}: ${title}`; + + console.log('\nšŸ”„ Setting up Git...'); + + try { + // Check if we're in a git repository + runGitCommand('git rev-parse --git-dir', { silent: true }); + + // Create and checkout new branch + console.log(`šŸ“ Creating branch: ${branchName}`); + runGitCommand(`git checkout -b ${branchName}`); + + // Add the new file + console.log(`šŸ“ Adding file: ${filePath}`); + runGitCommand(`git add ${filePath}`); + + // Commit the changes + console.log(`šŸ’¾ Committing changes...`); + runGitCommand(`git commit -m "${commitMessage}"`); + + // Push the branch + console.log(`šŸš€ Pushing branch to remote...`); + runGitCommand(`git push -u origin ${branchName}`); + + console.log(`\nāœ… Git operations completed successfully!`); + console.log(`🌿 Branch: ${branchName}`); + console.log(`šŸ“ Commit: ${commitMessage}`); + + return branchName; + } catch (error) { + console.log(`\nāš ļø Git operations failed. You can manually commit later:`); + console.log(` git add ${filePath}`); + console.log(` git commit -m "${commitMessage}"`); + return null; + } +} + +function createPullRequest(branchName, title, fileType) { + console.log('\nšŸ”— Creating Pull Request...'); + + try { + // Try to get the repository info + const remoteUrl = runGitCommand('git config --get remote.origin.url', { silent: true }).trim(); + + if (remoteUrl.includes('github.com')) { + const repoMatch = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/); + if (repoMatch) { + const [, owner, repo] = repoMatch; + const prUrl = `https://github.com/${owner}/${repo}/compare/${branchName}?expand=1`; + + console.log(`\nšŸŽ‰ Pull Request ready to create!`); + console.log(`šŸ”— Click here to create the PR: ${prUrl}`); + console.log(`\nšŸ“‹ Suggested PR details:`); + console.log(` Title: Add ${fileType}: ${title}`); + console.log(` Description: Created new ${fileType} file with proper YAML frontmatter and linting compliance.`); + + return prUrl; + } + } + + console.log(`\nšŸ“ To create a Pull Request:`); + console.log(` 1. Go to your repository on GitHub`); + console.log(` 2. You should see a banner to create a PR for branch: ${branchName}`); + console.log(` 3. Use title: "Add ${fileType}: ${title}"`); + + } catch (error) { + console.log(`\nāš ļø Could not generate PR link automatically.`); + console.log(` Please create a PR manually for branch: ${branchName}`); + } +} + +// Main function to create workshop file +async function createWorkshopFile() { + console.log('\nšŸŽÆ Creating a Workshop File\n'); + console.log('šŸ“‹ Linting Requirements:'); + console.log(' • Title must be ≤ 60 characters'); + console.log(' • Meta description must be ≤ 160 characters\n'); + + // Get basic info + let title = await askQuestion('šŸ“ Workshop Title: '); + while (title.length > 60) { + console.log(validateTitle(title)); + title = await askQuestion('šŸ“ Workshop Title (shortened): '); + } + console.log(validateTitle(title)); + + let metaDesc = await askQuestion('šŸ“„ Meta Description: '); + while (metaDesc.length > 160) { + console.log(validateMetaDesc(metaDesc)); + metaDesc = await askQuestion('šŸ“„ Meta Description (shortened): '); + } + console.log(validateMetaDesc(metaDesc)); + + const description = await askQuestion('šŸ“– Full Description: '); + + // Get learning objectives + console.log('\nšŸŽ“ Learning Objectives (enter one per line, empty line to finish):'); + const learn = []; + while (true) { + const objective = await askQuestion(' • '); + if (!objective) break; + learn.push(objective); + } + + // Get presenter info + const presenterName = await askQuestion('šŸ‘¤ Presenter Name: '); + const presenterRole = await askQuestion('šŸ’¼ Presenter Role: '); + + // Get event details + const date = await askQuestion('šŸ“… Date (YYYY-MM-DD): '); + const time = await askQuestion('ā° Time (HH:MM): '); + const timezone = await askQuestion('šŸŒ Timezone (EDT/EST/PDT/PST): '); + const duration = await askQuestion('ā±ļø Duration (e.g., "60 minutes"): '); + + // Get tags + const level = await askQuestion('šŸ“Š Level (Beginner/Intermediate/Advanced): '); + const topics = await askQuestion('šŸ·ļø Topics (comma-separated): ').split(',').map(t => t.trim()).filter(t => t); + const languages = await askQuestion('šŸ’» Languages (comma-separated): ').split(',').map(t => t.trim()).filter(t => t); + const clouds = await askQuestion('ā˜ļø Clouds (comma-separated): ').split(',').map(t => t.trim()).filter(t => t); + + // Get form IDs + const hubspotFormId = await askQuestion('šŸ“‹ HubSpot Form ID: '); + const salesforceCampaignId = await askQuestion('šŸ“Š Salesforce Campaign ID: '); + + // Create folder and file + const slug = createSlug(title); + const folderPath = path.join('content', 'events', slug); + const filePath = path.join(folderPath, 'index.md'); + + // Create directory if it doesn't exist + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + + // Generate content + const sortableDate = formatSortableDate(date, time, timezone); + const endTime = new Date(new Date(`${date}T${time}`).getTime() + (parseInt(duration) * 60000)); + const endDate = endTime.toISOString().replace('Z', timezone === 'EDT' ? '-04:00' : + timezone === 'EST' ? '-05:00' : + timezone === 'PDT' ? '-07:00' : + timezone === 'PST' ? '-08:00' : '-00:00'); + + const content = `--- +# Name of the event, <= 60 characters +title: "${title}" +meta_desc: ${metaDesc} +meta_image: + +# A featured webinar will display first in the list. +featured: false + +# Webinars with unlisted as true will not be shown on the webinar list +unlisted: false + +# Gated webinars will have a registration form and the user will need +# to fill out the form before viewing. +gated: true + +# The layout of the landing page. +type: webinars + +# External webinars will link to an external page instead of a webinar +# landing/registration page. If the webinar is external you will need +# set the 'block_external_search_index' flag to true so Google does not index +# the webinar page created. +external: false +block_external_search_index: false + +# The url slug for the webinar landing page. If this is an external +# webinar, use the external URL as the value here. +url_slug: ${slug} + +# Content for the left hand side section of the page. +main: + # Webinar title. + title: "${title}" + event_type: workshop # workshop | event + + # URL for embedding a URL for ungated webinars. + youtube_url: + + # Sortable date. The datetime Hugo will use to sort the webinars in date order. + sortable_date: ${sortableDate} + + # Duration of the webinar. + duration: ${duration} + + # "virtual" will be shown under "show virtual events only", otherwise shown as City, State (seattle, wa) + location: virtual + + # Description of the webinar. + description: | + ${description} + learn: +${learn.map(item => ` - ${item}`).join('\n')} + + # The webinar presenters + presenters: + - name: ${presenterName} + role: ${presenterRole} + photo: /images/team/${presenterName.toLowerCase().replace(/\s+/g, '-')}.jpg + + # case-sensitive + tags: + level: ${level} # Beginner, Intermediate, Advanced + topics: [${topics.map(t => `"${t}"`).join(', ')}] + languages: [${languages.map(l => `"${l}"`).join(', ')}] + clouds: [${clouds.map(c => `"${c}"`).join(', ')}] + +# The right hand side form section. +form: + # HubSpot form id. + hubspot_form_id: ${hubspotFormId} + salesforce_campaign_id: ${salesforceCampaignId} + +event_data: + name: "${title}" + start_date: ${sortableDate} + end_date: ${endDate} + url: "https://www.pulumi.com/resources/${slug}/" + description: | + ${description} +---`; + + // Write file + fs.writeFileSync(filePath, content); + + console.log(`\nāœ… Workshop file created successfully!`); + console.log(`šŸ“ Location: ${filePath}`); + console.log(`šŸ”— URL slug: ${slug}`); + + // Ask about Git operations + const useGit = await askQuestion('\nšŸ”„ Would you like to commit this to Git and create a PR? (y/n): '); + + if (useGit.toLowerCase() === 'y' || useGit.toLowerCase() === 'yes') { + const branchName = createBranchAndCommit(filePath, title, 'workshop'); + if (branchName) { + createPullRequest(branchName, title, 'workshop'); + } + } else { + console.log('\nšŸ“ To commit manually later:'); + console.log(` git add ${filePath}`); + console.log(` git commit -m "Add workshop: ${title}"`); + } +} + +// Main function to create event file +async function createEventFile() { + console.log('\nšŸŽÆ Creating an Event File\n'); + console.log('šŸ“‹ Linting Requirements:'); + console.log(' • Title must be ≤ 60 characters'); + console.log(' • Meta description must be ≤ 160 characters\n'); + + // Get basic info + let title = await askQuestion('šŸ“ Event Title: '); + while (title.length > 60) { + console.log(validateTitle(title)); + title = await askQuestion('šŸ“ Event Title (shortened): '); + } + console.log(validateTitle(title)); + + let metaDesc = await askQuestion('šŸ“„ Meta Description: '); + while (metaDesc.length > 160) { + console.log(validateMetaDesc(metaDesc)); + metaDesc = await askQuestion('šŸ“„ Meta Description (shortened): '); + } + console.log(validateMetaDesc(metaDesc)); + + const description = await askQuestion('šŸ“– Full Description: '); + + // Get event details + const date = await askQuestion('šŸ“… Date (YYYY-MM-DD): '); + const time = await askQuestion('ā° Time (HH:MM): '); + const timezone = await askQuestion('šŸŒ Timezone (EDT/EST/PDT/PST): '); + const duration = await askQuestion('ā±ļø Duration (e.g., "2 hours"): '); + const location = await askQuestion('šŸ“ Location (e.g., "Atlanta, GA"): '); + + // Get external URL + const externalUrl = await askQuestion('šŸ”— External Registration URL: '); + + // Create folder and file + const slug = createSlug(title); + const folderPath = path.join('content', 'events', slug); + const filePath = path.join(folderPath, 'index.md'); + + // Create directory if it doesn't exist + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + + // Generate content + const sortableDate = formatSortableDate(date, time, timezone); + + const content = `--- +# Name of the event, <= 60 characters +title: ${title} +meta_desc: ${metaDesc} +meta_image: + +# A featured webinar will display first in the list. +featured: false + +# Webinars with unlisted as true will not be shown on the webinar list +unlisted: false + +# Gated webinars will have a registration form and the user will need +# to fill out the form before viewing. +gated: false + +# The layout of the landing page. +type: webinars + +# External webinars will link to an external page instead of a webinar +# landing/registration page. If the webinar is external you will need +# set the 'block_external_search_index' flag to true so Google does not index +# the webinar page created. +external: true +block_external_search_index: true + +# The url slug for the webinar landing page. If this is an external +# webinar, use the external URL as the value here. +url_slug: ${externalUrl} + +# Content for the left hand side section of the page. +main: + # Webinar title. + title: ${title} + + event_type: event # workshop | event + + # URL for embedding a URL for ungated webinars. + youtube_url: + + # Sortable date. The datetime Hugo will use to sort the webinars in date order. + sortable_date: ${sortableDate} + + # Duration of the webinar. + duration: ${duration} + + # "virtual" will be shown under "show virtual events only", otherwise shown as City, State (seattle, wa) + location: ${location} + + # Description of the webinar. + description: ${description} + + # The webinar presenters + presenters: + + # case-sensitive + tags: + level: # Beginner, Intermediate, Advanced + topics: [] + languages: [] + clouds: [] + +# The right hand side form section. +form: + # HubSpot form id. + hubspot_form_id: + salesforce_campaign_id: +---`; + + // Write file + fs.writeFileSync(filePath, content); + + console.log(`\nāœ… Event file created successfully!`); + console.log(`šŸ“ Location: ${filePath}`); + console.log(`šŸ”— External URL: ${externalUrl}`); + + // Ask about Git operations + const useGit = await askQuestion('\nšŸ”„ Would you like to commit this to Git and create a PR? (y/n): '); + + if (useGit.toLowerCase() === 'y' || useGit.toLowerCase() === 'yes') { + const branchName = createBranchAndCommit(filePath, title, 'event'); + if (branchName) { + createPullRequest(branchName, title, 'event'); + } + } else { + console.log('\nšŸ“ To commit manually later:'); + console.log(` git add ${filePath}`); + console.log(` git commit -m "Add event: ${title}"`); + } +} + +// Main function +async function main() { + console.log('šŸš€ Pulumi Event/Workshop File Creator\n'); + + const fileType = await askQuestion('What would you like to create? (workshop/event): '); + + if (fileType.toLowerCase() === 'workshop') { + await createWorkshopFile(); + } else if (fileType.toLowerCase() === 'event') { + await createEventFile(); + } else { + console.log('āŒ Invalid option. Please choose "workshop" or "event".'); + } + + rl.close(); +} + +// Handle Ctrl+C gracefully +process.on('SIGINT', () => { + console.log('\nšŸ‘‹ Goodbye!'); + rl.close(); + process.exit(0); +}); + +// Run the script +main().catch(console.error); From 80cd319653258d4c43f8c05f5d282e0c2662dc20 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 3 Sep 2025 14:57:00 -0700 Subject: [PATCH 2/2] Add workshop: Testing this sworkshop script! --- .../testing-this-sworkshop-script/index.md | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 content/events/testing-this-sworkshop-script/index.md diff --git a/content/events/testing-this-sworkshop-script/index.md b/content/events/testing-this-sworkshop-script/index.md new file mode 100644 index 000000000000..1da308a4f0f3 --- /dev/null +++ b/content/events/testing-this-sworkshop-script/index.md @@ -0,0 +1,83 @@ +--- +# Name of the event, <= 60 characters +title: "Testing this sworkshop script!" +meta_desc: This is a great meta description, it should be long enough. +meta_image: + +# A featured webinar will display first in the list. +featured: false + +# Webinars with unlisted as true will not be shown on the webinar list +unlisted: false + +# Gated webinars will have a registration form and the user will need +# to fill out the form before viewing. +gated: true + +# The layout of the landing page. +type: webinars + +# External webinars will link to an external page instead of a webinar +# landing/registration page. If the webinar is external you will need +# set the 'block_external_search_index' flag to true so Google does not index +# the webinar page created. +external: false +block_external_search_index: false + +# The url slug for the webinar landing page. If this is an external +# webinar, use the external URL as the value here. +url_slug: testing-this-sworkshop-script + +# Content for the left hand side section of the page. +main: + # Webinar title. + title: "Testing this sworkshop script!" + event_type: workshop # workshop | event + + # URL for embedding a URL for ungated webinars. + youtube_url: + + # Sortable date. The datetime Hugo will use to sort the webinars in date order. + sortable_date: 2025-09-30T19:00:00.000-05:00 + + # Duration of the webinar. + duration: 90 minutes + + # "virtual" will be shown under "show virtual events only", otherwise shown as City, State (seattle, wa) + location: virtual + + # Description of the webinar. + description: | + Some more details here - you'll love this workshop! Woohoo! + learn: + - Learn + - Love Pulumi + - Have fun! + + # The webinar presenters + presenters: + - name: Pulumipus + role: Sr Engineer + photo: /images/team/pulumipus.jpg + + # case-sensitive + tags: + level: begineer # Beginner, Intermediate, Advanced + topics: ["AI", "Secrurity"] + languages: ["TypeScript", "Python"] + clouds: ["AWS"] + +# The right hand side form section. +form: + # HubSpot form id. + hubspot_form_id: 123 + salesforce_campaign_id: 456 + +event_data: + name: "Testing this sworkshop script!" + start_date: 2025-09-30T19:00:00.000-05:00 + end_date: 2025-09-30T20:30:00.000-05:00 + url: "https://www.pulumi.com/resources/testing-this-sworkshop-script/" + description: | + Some more details here - you'll love this workshop! Woohoo! +--- \ No newline at end of file