Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/calendar-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Sync Google Calendar

on:
push:
branches: [main]
paths:
- 'calendar/**'

# Never run two syncs at once
concurrency:
group: calendar-sync

permissions:
contents: read

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the push's previous commit is needed to diff against
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci --no-audit --no-fund
working-directory: calendar/scripts
- name: Sync
run: |
# Only sync what changed since the previous tip of main.
# On the very first push there is no previous commit: diff against the empty tree.
if [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
export BASE_SHA=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
node calendar/scripts/sync.mjs
env:
BASE_SHA: ${{ github.event.before }}
GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
GOOGLE_CALENDAR_ID: ${{ secrets.GOOGLE_CALENDAR_ID }}
18 changes: 18 additions & 0 deletions .github/workflows/calendar-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Validate calendar

on:
pull_request:
paths:
- 'calendar/**'

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci --no-audit --no-fund
working-directory: calendar/scripts
- run: node calendar/scripts/sync.mjs --validate
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.idea/
78 changes: 78 additions & 0 deletions calendar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Working sessions calendar

This folder holds data to modify the calendar at https://calendar.graphql.org

Every event of the working sessions calendar lives in this folder as a small YAML file.
When a change to this folder is merged to `main`, a GitHub Action updates the shared Google Calendar automatically. **Never edit the Google Calendar by hand**: manual changes to synced events are overwritten on the next sync.

## One file per event

```
calendar/<year>/<month>-<day>-<slug>.yaml
```

For example `calendar/2026/11-04-working-session.yaml` is an event on 4 November 2026. Month and day are always two digits. The date of the event comes from the file name.

The optional slug (lowercase letters, digits and dashes) lets you have **several events on the same day**: `11-04-working-session.yaml` and `11-04-social.yaml` are two separate events. A day can have at most one event without a slug (`11-04.yaml`), and each slug can be used once per day.

The slug is not shown in the calendar, but it identifies the event: **renaming a file to change its slug removes the old calendar event and creates a new one**.

## File format

```yaml
startTime: "16:00"
endTime: "17:00"
timeZone: Europe/Paris
title: GraphQL Working Session
location: https://zoom.us/j/0000000000
description: Agenda and notes for this session.
link: https://github.com/graphql/working-sessions
```

| Property | Description |
| ------------- | ------------------------------------------------------------------------ |
| `startTime` | Start time, 24h `HH:MM`, in `timeZone`. **Put it in quotes.** |
| `endTime` | End time, 24h `HH:MM`, in `timeZone`. Must be after `startTime`. |
| `timeZone` | [IANA time zone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g. `America/New_York`, `Europe/Paris`, `UTC`. |
| `title` | Event title. |
| `location` | Free text: a video call URL, an address, etc. |
| `description` | Free text description. |
| `link` | `http(s)` URL with more information (agenda, notes, ...). It is also appended to the calendar event description. |

All properties are required and no other property is allowed.

## Adding an event

1. Create `calendar/<year>/<month>-<day>[-<slug>].yaml` with the properties above.
2. Open a pull request. A check validates your file and reports errors on the offending files.
3. Once the PR is merged, the event shows up in the Google Calendar within a minute or so.

## Editing an event

Edit the file and open a pull request. The existing calendar event is updated in place.

To **move an event to another day** (or change its slug), rename the file. The old event is removed and a new one is created.

## Removing an event

Delete the file in a pull request. The event is removed from the Google Calendar on merge.

## Validation

You can check your changes locally (Node 20+):

```sh
cd calendar/scripts && npm ci && cd -
node calendar/scripts/sync.mjs --validate
```

## Maintainers: setup

The sync workflow (`.github/workflows/calendar-sync.yml`) needs two repository secrets:

- `GOOGLE_SERVICE_ACCOUNT_KEY`: the JSON key of a Google Cloud service account (with the Google Calendar API enabled).
- `GOOGLE_CALENDAR_ID`: the ID of the target calendar (Calendar settings → "Integrate calendar").

Share the target calendar with the service account's email address with the **"Make changes to events"** permission.

The workflow is incremental: on each push to `main` it only creates, updates or deletes the events of the files that changed in that push. Events not backed by a file in this folder are never touched.
Loading
Loading