Skip to content

MPL license file headers - #33

Open
devmount wants to merge 5 commits into
thunderbird:mainfrom
devmount:enhancements/29-license-file-headers
Open

devmount wants to merge 5 commits into
thunderbird:mainfrom
devmount:enhancements/29-license-file-headers

Conversation

@devmount

@devmount devmount commented Sep 2, 2026 •

Copy link
Copy Markdown

What changed?

This PR provides an add-license-headers.sh tool that checks or adds MPL 2.0 license headers to source files. The script is capable of:

# Adding missing headers to every tracked file in the repo
./scripts/add-license-headers.sh

# Only checking files (used by CI) - exits 1 and lists files with missing header license
./scripts/add-license-headers.sh --check --base main

# Only operating on specific files
./scripts/add-license-headers.sh -- path/to/file.py path/to/other.vue

The script only touches file types a comment style is known for (see scripts/license-comment-styles.conf). Everything else (images, lockfiles, Markdown, JSON, ...) is left alone.

I also implemented a .license-ignore in gitignore syntax style to exclude directories or files from the checker.

Optionally both files can be overridden by repositories to provide own comment styles or ignore list.

A small test suite of 34 tests was added covering different file types and cases.

See see docs/license-headers.md for more documentation.

Why?

MPL is a per-file license. This tool tries to provide a low-effort way to check repositories for missing license entries and include that in their CI.

Limitations and Notes

  • The "file mode" (operating on specific files given as parameter) of the script is currently unrestricted to any path, even outside the repository. Is that something we want to change?
  • To keep things simple for now, the license detection basically breaks down to finding the substring 'This Source Code Form is subject to the terms of the Mozilla Public' in a file. This is the first line that's always the same for every comment style. We might want to extend on this to check for more (e.g. the second line too License, v. 2.0. If a copy of the MPL was not distributed with this) and also to check for the top of the file (not implemented yet).
  • This needs some thorough testing. I tested it locally with the Appointment repo flagging 313 files in need of a license header (see file list below).
  • AI Disclaimer: The script was written by me, reviewed and extended by Claude Code in multiple iterations (adding the help text, handling the different comment styles, handling symlinks). Documentation and tests were written by Claude Code, reviewed by me.

Applicable Issues

Closes #29

Screenshots

No screenshot but a log of the detected files for Appointment:

Missing MPL license header in 313 file(s):
  backend/scripts/dummy_setup.py
  backend/scripts/entry.sh
  backend/src/appointment/celery_app.py
  backend/src/appointment/commands/backfill_google_channels.py
  backend/src/appointment/commands/download_legal.py
  backend/src/appointment/commands/generate_documentation_pages.py
  backend/src/appointment/commands/refresh_zoom_tokens.py
  backend/src/appointment/commands/renew_google_channels.py
  backend/src/appointment/commands/setup.py
  backend/src/appointment/commands/update_db.py
  backend/src/appointment/controller/apis/accounts_client.py
  backend/src/appointment/controller/apis/google_client.py
  backend/src/appointment/controller/apis/oidc_client.py
  backend/src/appointment/controller/apis/zoom_client.py
  backend/src/appointment/controller/auth.py
  backend/src/appointment/controller/calendar.py
  backend/src/appointment/controller/data.py
  backend/src/appointment/controller/external_connection.py
  backend/src/appointment/controller/google_watch.py
  backend/src/appointment/controller/mailer.py
  backend/src/appointment/controller/zoom.py
  backend/src/appointment/database/models.py
  backend/src/appointment/database/repo/__init__.py
  backend/src/appointment/database/repo/appointment.py
  backend/src/appointment/database/repo/attendee.py
  backend/src/appointment/database/repo/availability.py
  backend/src/appointment/database/repo/calendar.py
  backend/src/appointment/database/repo/external_connection.py
  backend/src/appointment/database/repo/google_calendar_channel.py
  backend/src/appointment/database/repo/schedule.py
  backend/src/appointment/database/repo/slot.py
  backend/src/appointment/database/repo/subscriber.py
  backend/src/appointment/database/schemas.py
  backend/src/appointment/defines.py
  backend/src/appointment/dependencies/auth.py
  backend/src/appointment/dependencies/database.py
  backend/src/appointment/dependencies/google.py
  backend/src/appointment/dependencies/metrics.py
  backend/src/appointment/dependencies/zoom.py
  backend/src/appointment/exceptions/account_api.py
  backend/src/appointment/exceptions/calendar.py
  backend/src/appointment/exceptions/fxa_api.py
  backend/src/appointment/exceptions/google_api.py
  backend/src/appointment/exceptions/misc.py
  backend/src/appointment/exceptions/validation.py
  backend/src/appointment/exceptions/zoom_api.py
  backend/src/appointment/l10n.py
  backend/src/appointment/l10n/de/email.ftl
  backend/src/appointment/l10n/de/fields.ftl
  backend/src/appointment/l10n/de/main.ftl
  backend/src/appointment/l10n/en/email.ftl
  backend/src/appointment/l10n/en/fields.ftl
  backend/src/appointment/l10n/en/main.ftl
  backend/src/appointment/logs.py
  backend/src/appointment/main.py
  backend/src/appointment/middleware/SanitizeMiddleware.py
  backend/src/appointment/middleware/l10n.py
  backend/src/appointment/routes/account.py
  backend/src/appointment/routes/api.py
  backend/src/appointment/routes/auth.py
  backend/src/appointment/routes/caldav.py
  backend/src/appointment/routes/commands.py
  backend/src/appointment/routes/google.py
  backend/src/appointment/routes/metrics.py
  backend/src/appointment/routes/schedule.py
  backend/src/appointment/routes/subscriber.py
  backend/src/appointment/routes/webhooks.py
  backend/src/appointment/routes/zoom.py
  backend/src/appointment/tasks/__init__.py
  backend/src/appointment/tasks/emails.py
  backend/src/appointment/tasks/google.py
  backend/src/appointment/tasks/health.py
  backend/src/appointment/tasks/locks.py
  backend/src/appointment/tasks/zoom.py
  backend/src/appointment/templates/email/cancelled.jinja2
  backend/src/appointment/templates/email/confirm.jinja2
  backend/src/appointment/templates/email/errors/zoom_invite_failed.jinja2
  backend/src/appointment/templates/email/includes/base.jinja2
  backend/src/appointment/templates/email/includes/footer.jinja2
  backend/src/appointment/templates/email/invite.jinja2
  backend/src/appointment/templates/email/new_booking.jinja2
  backend/src/appointment/templates/email/pending.jinja2
  backend/src/appointment/templates/email/rejected.jinja2
  backend/src/appointment/templates/email/support.jinja2
  backend/src/appointment/utils.py
  backend/test/__init__.py
  backend/test/conftest.py
  backend/test/defines.py
  backend/test/factory/appointment_factory.py
  backend/test/factory/attendee_factory.py
  backend/test/factory/calendar_factory.py
  backend/test/factory/external_connection_factory.py
  backend/test/factory/schedule_factory.py
  backend/test/factory/slot_factory.py
  backend/test/factory/subscriber_factory.py
  backend/test/integration/test_account.py
  backend/test/integration/test_appointment.py
  backend/test/integration/test_auth.py
  backend/test/integration/test_caldav_routes.py
  backend/test/integration/test_calendar.py
  backend/test/integration/test_general.py
  backend/test/integration/test_google_calendar_webhook.py
  backend/test/integration/test_middleware.py
  backend/test/integration/test_profile.py
  backend/test/integration/test_schedule.py
  backend/test/integration/test_subscriber.py
  backend/test/integration/test_webhooks.py
  backend/test/integration/test_zoom.py
  backend/test/unit/test_accounts_client.py
  backend/test/unit/test_auth_controller.py
  backend/test/unit/test_auth_dependency.py
  backend/test/unit/test_cache.py
  backend/test/unit/test_cache_bust.py
  backend/test/unit/test_calendar_tools.py
  backend/test/unit/test_commands.py
  backend/test/unit/test_data.py
  backend/test/unit/test_google_client.py
  backend/test/unit/test_google_webhook.py
  backend/test/unit/test_handle_availability_decision.py
  backend/test/unit/test_mailer.py
  backend/test/unit/test_models.py
  backend/test/unit/test_schedule_availability.py
  backend/test/unit/test_schedule_repo.py
  backend/test/unit/test_schemas.py
  backend/test/unit/test_subscriber_repo.py
  backend/test/unit/test_utils.py
  backend/test/unit/test_utils_timestamps.py
  docker-compose.yml
  frontend/docker/docker-entrypoint.d/40-appointment-config.sh
  frontend/docker/test-nginx-config.sh
  frontend/eslint.config.js
  frontend/index.html
  frontend/pnpm-workspace.yaml
  frontend/public/config.js
  frontend/src/App.vue
  frontend/src/assets/docs/en/using-zoom.html
  frontend/src/assets/legal/en/privacy.html
  frontend/src/assets/legal/en/terms.html
  frontend/src/assets/styles/colours.css
  frontend/src/assets/styles/fonts.css
  frontend/src/assets/styles/main.css
  frontend/src/assets/styles/variables.css
  frontend/src/components/AsideNavButton.vue
  frontend/src/components/CalDavProvider.vue
  frontend/src/components/DataTable.vue
  frontend/src/components/FadeInOutTransition.vue
  frontend/src/components/FooterBar.vue
  frontend/src/components/NavBar.vue
  frontend/src/components/NavBarMobile.vue
  frontend/src/components/UserMenu.vue
  frontend/src/composables/authSchemes.ts
  frontend/src/composables/dayjs.ts
  frontend/src/composables/i18n.ts
  frontend/src/composables/oidcUserManager.ts
  frontend/src/composables/posthog.ts
  frontend/src/composables/useQueryParamState.ts
  frontend/src/config.ts
  frontend/src/definitions.ts
  frontend/src/elements/AlertBox.vue
  frontend/src/elements/ContainerBox.vue
  frontend/src/elements/DropDown.vue
  frontend/src/elements/EventPopup.vue
  frontend/src/elements/ListPagination.vue
  frontend/src/elements/LoadingSpinner.vue
  frontend/src/elements/NavBarItem.vue
  frontend/src/elements/SiteNotification.vue
  frontend/src/elements/SlidingPanel.vue
  frontend/src/elements/arts/ArtInvalidLink.vue
  frontend/src/elements/arts/ArtSuccessfulBooking.vue
  frontend/src/keys.ts
  frontend/src/main.ts
  frontend/src/models.ts
  frontend/src/router.ts
  frontend/src/stores/alert-store.ts
  frontend/src/stores/appointment-store.ts
  frontend/src/stores/availability-store.ts
  frontend/src/stores/booking-view-store.ts
  frontend/src/stores/calendar-store.ts
  frontend/src/stores/external-connections-store.ts
  frontend/src/stores/ftue-store.ts
  frontend/src/stores/schedule-store.ts
  frontend/src/stores/settings-store.ts
  frontend/src/stores/user-store.ts
  frontend/src/utils.ts
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilityBookingWindowPill.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilityCalendarSelect.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilityCopyDropdown.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilityMinimumNoticePill.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilitySelect.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/components/AvailabilityTimezoneSelect.vue
  frontend/src/views/AvailabilityView/components/AvailabilitySettings/index.vue
  frontend/src/views/AvailabilityView/components/BookingPageDetails/index.vue
  frontend/src/views/AvailabilityView/components/BookingPageLink/components/RefreshLinkModal.vue
  frontend/src/views/AvailabilityView/components/BookingPageLink/index.vue
  frontend/src/views/AvailabilityView/components/RadioGroupPill.vue
  frontend/src/views/AvailabilityView/index.vue
  frontend/src/views/BookerView/components/BookingViewError.vue
  frontend/src/views/BookerView/components/BookingViewSlotSelection.vue
  frontend/src/views/BookerView/components/BookingViewSuccess.vue
  frontend/src/views/BookerView/components/SlotSelectionAside.vue
  frontend/src/views/BookerView/components/SlotSelectionHeader.vue
  frontend/src/views/BookerView/index.vue
  frontend/src/views/BookingConfirmationView.vue
  frontend/src/views/BookingsView/components/AppointmentSlidingPanel.vue
  frontend/src/views/BookingsView/components/AppointmentSlidingPanelBookingConfirmation.vue
  frontend/src/views/BookingsView/components/AppointmentSlidingPanelCancel.vue
  frontend/src/views/BookingsView/components/AppointmentSlidingPanelDetails.vue
  frontend/src/views/BookingsView/components/AppointmentSlidingPanelModify.vue
  frontend/src/views/BookingsView/components/DateRequestedAppointmentItem.vue
  frontend/src/views/BookingsView/components/DateRequestedAppointments.vue
  frontend/src/views/BookingsView/components/MeetingDateAppointmentItem.vue
  frontend/src/views/BookingsView/components/MeetingDateAppointments.vue
  frontend/src/views/BookingsView/constants.ts
  frontend/src/views/BookingsView/index.vue
  frontend/src/views/DashboardView/components/QuickActionsSideBar.vue
  frontend/src/views/DashboardView/components/UserCalendarSync.vue
  frontend/src/views/DashboardView/components/WeekCalendar.vue
  frontend/src/views/DashboardView/components/WeekPicker.vue
  frontend/src/views/DashboardView/index.vue
  frontend/src/views/DocsView.vue
  frontend/src/views/FTUEView/components/GradientCheckCircle.vue
  frontend/src/views/FTUEView/components/ProviderCardButton.vue
  frontend/src/views/FTUEView/components/RadioProviderCardButton.vue
  frontend/src/views/FTUEView/components/StepTitle.vue
  frontend/src/views/FTUEView/index.vue
  frontend/src/views/FTUEView/steps/ConnectYourCalendarCalDavStep.vue
  frontend/src/views/FTUEView/steps/ConnectYourCalendarGoogleStep.vue
  frontend/src/views/FTUEView/steps/ConnectYourCalendarStep.vue
  frontend/src/views/FTUEView/steps/CreateBookingPageStep.vue
  frontend/src/views/FTUEView/steps/SetYourAvailabilityStep.vue
  frontend/src/views/FTUEView/steps/SetupCompleteStep.vue
  frontend/src/views/FTUEView/steps/VideoMeetingLinkStep.vue
  frontend/src/views/HomeView.vue
  frontend/src/views/LegalView.vue
  frontend/src/views/LoginView.vue
  frontend/src/views/LogoutView.vue
  frontend/src/views/PostLoginView.vue
  frontend/src/views/SettingsView/components/AccountSettings.vue
  frontend/src/views/SettingsView/components/CaldavConnectModal.vue
  frontend/src/views/SettingsView/components/CaldavDisconnectModal.vue
  frontend/src/views/SettingsView/components/ConnectedApplications.vue
  frontend/src/views/SettingsView/components/DeleteAccountModal.vue
  frontend/src/views/SettingsView/components/GoogleDisconnectModal.vue
  frontend/src/views/SettingsView/components/Preferences.vue
  frontend/src/views/SettingsView/components/ZoomDisconnectModal.vue
  frontend/src/views/SettingsView/index.vue
  frontend/src/views/admin/SubscriberPanelView.vue
  frontend/src/views/admin/components/DeleteSubscriberModal.vue
  frontend/src/views/errors/NotAuthenticatedView.vue
  frontend/src/views/errors/RouteNotFoundView.vue
  frontend/test/components/BookerView.test.js
  frontend/test/components/FooterBar.test.js
  frontend/test/components/NavBar.test.js
  frontend/test/components/NavBarMobile.test.js
  frontend/test/config.test.ts
  frontend/test/pulumi/cloudfront-rewrite.test.js
  frontend/test/setup/fix-fetch.js
  frontend/test/stores/alert-store.test.js
  frontend/test/stores/appointment-store.test.js
  frontend/test/stores/booking-view-store.test.js
  frontend/test/stores/calendar-store.test.js
  frontend/test/stores/external-connections-store.test.js
  frontend/test/stores/ftue-store.test.js
  frontend/test/stores/schedule-store.test.js
  frontend/test/stores/user-store.test.js
  frontend/test/utils.test.js
  frontend/test/utils/with-setup.js
  frontend/vite.config.ts
  frontend/vitest.config.ts
  frontend/vitest.global-setup.ts
  pulumi/Pulumi.dev.yaml
  pulumi/Pulumi.prod.yaml
  pulumi/Pulumi.stage.yaml
  pulumi/Pulumi.yaml
  pulumi/__main__.py
  pulumi/cloudfront-rewrite.js
  pulumi/cloudfront.py
  pulumi/config.dev.yaml
  pulumi/config.prod.yaml
  pulumi/config.stage.yaml
  pulumi/fargate.py
  pulumi/redis.py
  pulumi/security_groups.py
  test/e2e/browserstack-desktop-nightly.yml
  test/e2e/browserstack-desktop.yml
  test/e2e/browserstack-mobile-nightly.yml
  test/e2e/browserstack-mobile.yml
  test/e2e/const/constants.ts
  test/e2e/pages/availability-page.ts
  test/e2e/pages/booking-page.ts
  test/e2e/pages/dashboard-page.ts
  test/e2e/pages/settings-page.ts
  test/e2e/pages/tb-accts-page.ts
  test/e2e/playwright.config.ts
  test/e2e/pnpm-workspace.yaml
  test/e2e/tests/desktop/access-booking-page.spec.ts
  test/e2e/tests/desktop/auth.desktop.setup.ts
  test/e2e/tests/desktop/availability-booking-details.spec.ts
  test/e2e/tests/desktop/availability-set.spec.ts
  test/e2e/tests/desktop/book-appointment.spec.ts
  test/e2e/tests/desktop/deployment-analysis-signin.spec.ts
  test/e2e/tests/desktop/settings-account.spec.ts
  test/e2e/tests/desktop/settings-connected-apps.spec.ts
  test/e2e/tests/desktop/settings-language.spec.ts
  test/e2e/tests/desktop/settings-start-of-week.spec.ts
  test/e2e/tests/desktop/settings-theme.spec.ts
  test/e2e/tests/mobile/book-appointment.spec.ts
  test/e2e/tests/mobile/settings-account.spec.ts
  test/e2e/tests/mobile/settings-connected-apps.spec.ts
  test/e2e/tests/mobile/settings-language.spec.ts
  test/e2e/tests/mobile/settings-start-of-week.spec.ts
  test/e2e/tests/mobile/settings-theme.spec.ts
  test/e2e/utils/utils.ts

Re-run '../pro/scripts/add-license-headers.sh' without --check to add them, then commit.

@devmount

devmount commented Sep 2, 2026

Copy link
Copy Markdown
Author

Poking @kewisch @davinotdavid @rwood-moz for review (since I cannot assign reviewers in this repo).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MPL license headers and CI check

1 participant