From 2a21afaebe3f6f5a551bfb9fdfb76404c9c2aa9e Mon Sep 17 00:00:00 2001 From: Howard Askew <19271569+howaskew@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:58:36 +0100 Subject: [PATCH 1/3] Update to schema.org 30.1 and pin the vocabulary version schema.org 30.1 moved 20 terms used by the models from pending to core, and its larger vocabulary makes tsc fail with TS2590 on the generated schemaOrgVocab.js. - Pin the schema.org vocabulary to the v30.1 GitHub tag instead of version/latest. schema.org/version// only serves the current release, so a pin to it would break when the next release ships. - Add // @ts-nocheck to the generated schemaOrgVocab.js only. It is pure data; the other generated files keep type checking. - Repoint sameAs/derivedFrom for the 20 terms from pending.schema.org to schema.org (25 references in 12 files). The pending namespace stays in meta.json. - Update the MODELS.md example and document the update process in README. This removes the 20 pending: entries from the generated JSON-LD context, so these terms now expand to https://schema.org/. Co-Authored-By: Claude Opus 5.5 (1M context) --- MODELS.md | 2 +- README.md | 12 ++++++++++++ scripts/build.js | 5 ++++- versions/2.x/models/BookingService.json | 4 ++-- versions/2.x/models/CourseInstance.json | 2 +- versions/2.x/models/Dataset.json | 2 +- versions/2.x/models/Event.json | 4 ++-- versions/2.x/models/EventSeries.json | 2 +- versions/2.x/models/FacilityUse.json | 2 +- versions/2.x/models/Organization.json | 2 +- versions/2.x/models/Person.json | 6 +++--- versions/2.x/models/Schedule.json | 16 ++++++++-------- versions/2.x/models/SessionSeries.json | 2 +- versions/2.x/models/VirtualLocation.json | 2 +- versions/2.x/models/WebAPI.json | 6 +++--- 15 files changed, 42 insertions(+), 27 deletions(-) diff --git a/MODELS.md b/MODELS.md index 04196122..46bd89d7 100644 --- a/MODELS.md +++ b/MODELS.md @@ -314,7 +314,7 @@ The minimum value of a number, inclusive. ```json "repeatCount": { "fieldName": "repeatCount", - "sameAs": "https://pending.schema.org/repeatCount", + "sameAs": "https://schema.org/repeatCount", "requiredType": "https://schema.org/Integer", "example": 3, "description": [ diff --git a/README.md b/README.md index 30d47cff..a086c7f2 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,18 @@ The test run will also include a run of [eslint](https://eslint.org/). To run th $ npm run test-no-lint ``` +### Updating schema.org + +The build downloads the schema.org vocabulary from a tagged release (`schemaOrgVocabUrl` in `scripts/build.js`) rather than the latest version, so a new schema.org release cannot break the build until the pin is changed. Pin to the GitHub tag URL: `schema.org/version//` only serves the current release, so a pin to it would break as soon as the next release is published. + +To move to a new schema.org release: + +1. Change the tag and release number in `schemaOrgVocabUrl`. +2. Run `npm test`. +3. If schema.org has moved terms from pending to core, tests fail with `... is not an accurate pending.schema.org reference ...`. Change the matching `sameAs` and `derivedFrom` values under `versions/2.x/models` from `https://pending.schema.org/` to `https://schema.org/`, and update any examples in [MODELS.md](MODELS.md). Leave the `pending` namespace in `meta.json`, as it is part of the published JSON-LD context. + +For example, schema.org 30.1 moved 20 terms used by these models from pending to core: `EventSeries`, `Schedule`, `VirtualLocation`, `WebAPI`, `byDay`, `byMonth`, `byMonthDay`, `documentation`, `eventAttendanceMode`, `eventSchedule`, `exceptDate`, `gender`, `hasCredential`, `jobTitle`, `maximumVirtualAttendeeCapacity`, `provider`, `repeatCount`, `repeatFrequency`, `scheduleTimezone` and `termsOfService`. + ### Adding models Add new models to the `versions/models` directory. diff --git a/scripts/build.js b/scripts/build.js index d3cf4036..061995c8 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -13,7 +13,9 @@ const renderModel = require('../src/helpers/renderModel'); const uniqueVersions = [...new Set(Object.values(versions))]; const distPath = path.join(__dirname, '..', 'src', 'dist'); - const schemaOrgVocabUrl = 'https://schema.org/version/latest/schemaorg-current-https.jsonld'; + // Pinned to a tagged schema.org release so a new release cannot break CI unannounced. + // schema.org/version// only serves the current release, so use the GitHub tag instead. + const schemaOrgVocabUrl = 'https://raw.githubusercontent.com/schemaorg/schemaorg/v30.1/data/releases/30.1/schemaorg-current-https.jsonld'; console.log('Fetching schema.org vocab...'); const schema = JSON.parse(request('GET', schemaOrgVocabUrl, { @@ -130,6 +132,7 @@ const renderModel = require('../src/helpers/renderModel'); fs.writeFileSync( path.join(distPath, 'schemaOrgVocab.js'), `/* eslint-disable */ + // @ts-nocheck - the schema.org vocabulary is too large for tsc to infer (TS2590). // This is a generated file. Do not edit manually. module.exports = ${JSON.stringify(schema)};`, () => {}, diff --git a/versions/2.x/models/BookingService.json b/versions/2.x/models/BookingService.json index 05113ce9..ba7098fb 100644 --- a/versions/2.x/models/BookingService.json +++ b/versions/2.x/models/BookingService.json @@ -79,7 +79,7 @@ }, "termsOfService": { "fieldName": "termsOfService", - "sameAs": "https://pending.schema.org/termsOfService", + "sameAs": "https://schema.org/termsOfService", "model": "ArrayOf#Terms", "description": [ "The terms of service of the Booking System." @@ -95,7 +95,7 @@ }, "hasCredential": { "fieldName": "hasCredential", - "sameAs": "https://pending.schema.org/hasCredential", + "sameAs": "https://schema.org/hasCredential", "requiredType": "https://schema.org/URL", "description": [ "A human and machine-readable certification file that asserts the service's conformance to established standards. The value of this property should be a URL that resolves to a valid and up-to-date OpenActive Test Suite Conformance Certificate for this implementation. Note this property is in EARLY RELEASE AND IS SUBJECT TO CHANGE, as the [Dataset API Discovery specification](https://openactive.io/dataset-api-discovery/EditorsDraft/) evolves." diff --git a/versions/2.x/models/CourseInstance.json b/versions/2.x/models/CourseInstance.json index 9362c43c..4e70153d 100644 --- a/versions/2.x/models/CourseInstance.json +++ b/versions/2.x/models/CourseInstance.json @@ -243,7 +243,7 @@ }, "eventSchedule": { "fieldName": "eventSchedule", - "sameAs": "https://pending.schema.org/eventSchedule", + "sameAs": "https://schema.org/eventSchedule", "model": "ArrayOf#Schedule", "description": [ "A an array of oa:Schedule or oa:PartialSchedule, which represents a recurrence pattern." diff --git a/versions/2.x/models/Dataset.json b/versions/2.x/models/Dataset.json index a7a75e41..47393b62 100644 --- a/versions/2.x/models/Dataset.json +++ b/versions/2.x/models/Dataset.json @@ -169,7 +169,7 @@ }, "documentation": { "fieldName": "documentation", - "sameAs": "https://pending.schema.org/documentation", + "sameAs": "https://schema.org/documentation", "requiredType": "https://schema.org/URL", "example": "https://permalink.openactive.io/dataset-site/open-data-documentation", "description": [ diff --git a/versions/2.x/models/Event.json b/versions/2.x/models/Event.json index 34ae6a54..b7eb913c 100644 --- a/versions/2.x/models/Event.json +++ b/versions/2.x/models/Event.json @@ -508,7 +508,7 @@ }, "maximumVirtualAttendeeCapacity": { "fieldName": "maximumVirtualAttendeeCapacity", - "sameAs": "https://pending.schema.org/maximumVirtualAttendeeCapacity", + "sameAs": "https://schema.org/maximumVirtualAttendeeCapacity", "requiredType": "https://schema.org/Integer", "description": [ "Indicates the maximum number of connections to a shared virtual space." @@ -660,7 +660,7 @@ }, "eventAttendanceMode": { "fieldName": "eventAttendanceMode", - "sameAs": "https://pending.schema.org/eventAttendanceMode", + "sameAs": "https://schema.org/eventAttendanceMode", "requiredType": "https://schema.org/EventAttendanceModeEnumeration", "description": [ "The eventAttendanceMode of an event indicates whether it occurs online, offline, or a mix." diff --git a/versions/2.x/models/EventSeries.json b/versions/2.x/models/EventSeries.json index ad756e0a..b656de17 100644 --- a/versions/2.x/models/EventSeries.json +++ b/versions/2.x/models/EventSeries.json @@ -2,7 +2,7 @@ "type": "EventSeries", "hasId": true, "subClassOf": "#Event", - "derivedFrom": "https://pending.schema.org/EventSeries", + "derivedFrom": "https://schema.org/EventSeries", "sampleId": "https://api.example.com/event-series/12345", "requiredFields": [ "type", diff --git a/versions/2.x/models/FacilityUse.json b/versions/2.x/models/FacilityUse.json index 5002af4f..8b5e92a7 100644 --- a/versions/2.x/models/FacilityUse.json +++ b/versions/2.x/models/FacilityUse.json @@ -310,7 +310,7 @@ }, "provider": { "fieldName": "provider", - "sameAs": "https://pending.schema.org/provider", + "sameAs": "https://schema.org/provider", "model": "#Organization", "description": [ "The organisation responsible for providing the facility" diff --git a/versions/2.x/models/Organization.json b/versions/2.x/models/Organization.json index d27d8714..1fe559db 100755 --- a/versions/2.x/models/Organization.json +++ b/versions/2.x/models/Organization.json @@ -331,7 +331,7 @@ }, "termsOfService": { "fieldName": "termsOfService", - "sameAs": "https://pending.schema.org/termsOfService", + "sameAs": "https://schema.org/termsOfService", "model": "ArrayOf#Terms", "description": [ "The terms of service of the Seller." diff --git a/versions/2.x/models/Person.json b/versions/2.x/models/Person.json index 3dc3e12c..f9455c48 100755 --- a/versions/2.x/models/Person.json +++ b/versions/2.x/models/Person.json @@ -183,7 +183,7 @@ }, "gender": { "fieldName": "gender", - "sameAs": "https://pending.schema.org/gender", + "sameAs": "https://schema.org/gender", "requiredType": "https://schema.org/GenderType", "alternativeTypes": [ "https://schema.org/Text" @@ -224,7 +224,7 @@ }, "jobTitle": { "fieldName": "jobTitle", - "sameAs": "https://pending.schema.org/jobTitle", + "sameAs": "https://schema.org/jobTitle", "requiredType": "https://schema.org/Text", "example": "Team Captain", "description": [ @@ -259,7 +259,7 @@ }, "termsOfService": { "fieldName": "termsOfService", - "sameAs": "https://pending.schema.org/termsOfService", + "sameAs": "https://schema.org/termsOfService", "model": "ArrayOf#Terms", "description": [ "The terms of service of the Seller." diff --git a/versions/2.x/models/Schedule.json b/versions/2.x/models/Schedule.json index 89edb413..a044b0f0 100755 --- a/versions/2.x/models/Schedule.json +++ b/versions/2.x/models/Schedule.json @@ -1,6 +1,6 @@ { "type": "Schedule", - "derivedFrom": "https://pending.schema.org/Schedule", + "derivedFrom": "https://schema.org/Schedule", "derivedFromPending": true, "hasId": false, "requiredFields": [ @@ -59,7 +59,7 @@ }, "byDay": { "fieldName": "byDay", - "sameAs": "https://pending.schema.org/byDay", + "sameAs": "https://schema.org/byDay", "requiredType": "ArrayOf#https://schema.org/DayOfWeek", "alternativeTypes": ["ArrayOf#https://schema.org/Text"], "example": ["https://schema.org/Monday"], @@ -70,7 +70,7 @@ }, "byMonth": { "fieldName": "byMonth", - "sameAs": "https://pending.schema.org/byMonth", + "sameAs": "https://schema.org/byMonth", "requiredType": "ArrayOf#https://schema.org/Integer", "example": [2], "description": [ @@ -79,7 +79,7 @@ }, "byMonthDay": { "fieldName": "byMonthDay", - "sameAs": "https://pending.schema.org/byMonthDay", + "sameAs": "https://schema.org/byMonthDay", "requiredType": "ArrayOf#https://schema.org/Integer", "example": [28], "description": [ @@ -115,7 +115,7 @@ }, "exceptDate": { "fieldName": "exceptDate", - "sameAs": "https://pending.schema.org/exceptDate", + "sameAs": "https://schema.org/exceptDate", "requiredType": "ArrayOf#https://schema.org/Date", "alternativeTypes": ["ArrayOf#https://schema.org/DateTime"], "example": [ @@ -142,7 +142,7 @@ }, "repeatCount": { "fieldName": "repeatCount", - "sameAs": "https://pending.schema.org/repeatCount", + "sameAs": "https://schema.org/repeatCount", "requiredType": "https://schema.org/Integer", "example": 3, "description": [ @@ -152,7 +152,7 @@ }, "repeatFrequency": { "fieldName": "repeatFrequency", - "sameAs": "https://pending.schema.org/repeatFrequency", + "sameAs": "https://schema.org/repeatFrequency", "requiredType": "https://schema.org/Duration", "example": "P1D", "description": [ @@ -195,7 +195,7 @@ }, "scheduleTimezone": { "fieldName": "scheduleTimezone", - "sameAs": "https://pending.schema.org/scheduleTimezone", + "sameAs": "https://schema.org/scheduleTimezone", "requiredType": "https://schema.org/Text", "example": "Europe/London", "description": [ diff --git a/versions/2.x/models/SessionSeries.json b/versions/2.x/models/SessionSeries.json index 95e6e680..ed6bbd47 100644 --- a/versions/2.x/models/SessionSeries.json +++ b/versions/2.x/models/SessionSeries.json @@ -141,7 +141,7 @@ }, "eventSchedule": { "fieldName": "eventSchedule", - "sameAs": "https://pending.schema.org/eventSchedule", + "sameAs": "https://schema.org/eventSchedule", "model": "ArrayOf#Schedule", "description": [ "A an array of oa:Schedule or oa:PartialSchedule, which represents a recurrence pattern." diff --git a/versions/2.x/models/VirtualLocation.json b/versions/2.x/models/VirtualLocation.json index ba8563e2..3d49c59c 100755 --- a/versions/2.x/models/VirtualLocation.json +++ b/versions/2.x/models/VirtualLocation.json @@ -1,6 +1,6 @@ { "type": "VirtualLocation", - "derivedFrom": "https://pending.schema.org/VirtualLocation", + "derivedFrom": "https://schema.org/VirtualLocation", "hasId": false, "requiredFields":[ "type" diff --git a/versions/2.x/models/WebAPI.json b/versions/2.x/models/WebAPI.json index e020318f..f7eb1ee9 100644 --- a/versions/2.x/models/WebAPI.json +++ b/versions/2.x/models/WebAPI.json @@ -1,6 +1,6 @@ { "type": "WebAPI", - "derivedFrom": "https://pending.schema.org/WebAPI", + "derivedFrom": "https://schema.org/WebAPI", "hasId": false, "validationMode": { "RPDEFeed": "noRequired", @@ -91,7 +91,7 @@ }, "documentation": { "fieldName": "documentation", - "sameAs": "https://pending.schema.org/documentation", + "sameAs": "https://schema.org/documentation", "requiredType": "https://schema.org/URL", "example": "https://permalink.openactive.io/dataset-site/open-booking-api-documentation", "description": [ @@ -100,7 +100,7 @@ }, "termsOfService": { "fieldName": "termsOfService", - "sameAs": "https://pending.schema.org/termsOfService", + "sameAs": "https://schema.org/termsOfService", "requiredType": "https://schema.org/URL", "example": "https://example.bookingsystem.com/terms", "description": [ From f381917944ed246ca19e300f33d5cc24b2d85cb3 Mon Sep 17 00:00:00 2001 From: Howard Askew <19271569+howaskew@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:14:23 +0100 Subject: [PATCH 2/3] ci: Make the npm publish safe to rerun and switch to the GitHub App - Push the version bump before publishing, so a rejected push stops the run before anything reaches npm. Previously npm could be ahead of GitHub, as happened to data-model-validator on 5 August 2026. - Remove the postpublish git push, and make publish-patch push before publishing too. - Use the OpenActive GitHub App token for checkout, push and dispatch instead of the PUBLIC_REPO_ACCESS_TOKEN personal access token. - Loop guard: skip runs started by openactive[bot], and add a skip-CI marker to the bump commit, so the guard no longer depends on email strings. - Scope the dispatch token to the target repository; without owner and repositories the App token only covers this repository. - Publish with NPM_TOKEN2, a repo-level token (the org NPM_TOKEN is rejected by npm). - Run one publish at a time, and update checkout, setup-node and repository-dispatch to current major versions. Supersedes #206 and #207. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/npm-test.yml | 4 +-- .github/workflows/publish-to-npm.yml | 50 +++++++++++++++++++++------- package.json | 3 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.github/workflows/npm-test.yml b/.github/workflows/npm-test.yml index c661ff6a..2f47c522 100644 --- a/.github/workflows/npm-test.yml +++ b/.github/workflows/npm-test.yml @@ -10,9 +10,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Use Node.js 18.17.1 - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 18.17.1 - name: Install diff --git a/.github/workflows/publish-to-npm.yml b/.github/workflows/publish-to-npm.yml index f02db902..f4c6180f 100644 --- a/.github/workflows/publish-to-npm.yml +++ b/.github/workflows/publish-to-npm.yml @@ -4,22 +4,37 @@ on: push: branches: [ master ] +# Never run two publishes at once; a second merge waits for the first to finish +concurrency: + group: publish-to-npm + cancel-in-progress: false + +permissions: + contents: read + jobs: publish: runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.author.email, 'openactive-bot@users.noreply.github.com') }} + # Loop guard: the version bump commit below is pushed by the OpenActive GitHub App, and also carries [skip ci] + if: ${{ github.actor != 'openactive[bot]' }} steps: + - name: Generate GitHub App Token + uses: actions/create-github-app-token@v1 + id: generate-token + with: + app-id: ${{ secrets.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: - token: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} + token: ${{ steps.generate-token.outputs.token }} - name: Identify run: | - git config user.name openactive-bot - git config user.email openactive-bot@users.noreply.github.com + git config --global user.name "openactive[bot]" + git config --global user.email 207210293+openactive[bot]@users.noreply.github.com - name: Use Node.js 18.17.1 - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 18.17.1 registry-url: https://registry.npmjs.org/ @@ -28,24 +43,35 @@ jobs: - name: Test run: npm test - name: Increment Version - run: npm version patch + run: npm version patch -m "%s [skip ci]" + # Push before publishing: if the push is rejected, nothing is published, so npm can never get ahead of GitHub + - name: Push version update + run: git push origin HEAD:master - name: Publish to npm run: npm publish env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - name: Push version update - run: git push + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN2 }} dispatch: needs: publish strategy: + fail-fast: false matrix: repo: ['OpenActive.NET', 'OpenActive.DatasetSite.NET', 'models-php', 'models-ruby', 'models-ts', 'data-model-validator-site', 'openactive.github.io', 'developer-documentation', 'openactive-test-suite'] runs-on: ubuntu-latest steps: + # Without owner and repositories, the token only covers this repository and cannot dispatch to others + - name: Generate GitHub App Token + uses: actions/create-github-app-token@v1 + id: generate-token + with: + app-id: ${{ secrets.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + owner: openactive + repositories: ${{ matrix.repo }} - name: Trigger tooling update - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: - token: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} + token: ${{ steps.generate-token.outputs.token }} repository: openactive/${{ matrix.repo }} event-type: data-models-update diff --git a/package.json b/package.json index dd5537e1..a356744e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "test": "npm run test-no-lint", "test-no-lint": "jasmine", "test-debug": "node --inspect-brk -i ./node_modules/jasmine/bin/jasmine.js", - "postpublish": "git push", - "publish-patch": "npm test && git pull && git push && npm version patch && npm publish" + "publish-patch": "npm test && git pull && npm version patch && git push && npm publish" } } From d788e910774dda38905886ebc9337fb52ea436bb Mon Sep 17 00:00:00 2001 From: Howard Askew <19271569+howaskew@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:36:36 +0100 Subject: [PATCH 3/3] ci: Wait for the new version on npm before dispatching npm can take several minutes to make a newly published version available. The downstream repositories install @latest as soon as they are dispatched, so they could rebuild with the previous version. On rpde-validator's first run of the new workflow (20 October 2026) the dispatches fired while npm was still processing 3.0.3572. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/publish-to-npm.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/publish-to-npm.yml b/.github/workflows/publish-to-npm.yml index f4c6180f..b3152ddb 100644 --- a/.github/workflows/publish-to-npm.yml +++ b/.github/workflows/publish-to-npm.yml @@ -51,6 +51,21 @@ jobs: run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN2 }} + # npm may take several minutes to make a new version available; downstream repositories install @latest + # as soon as they are dispatched, so wait until the new version can be fetched before dispatching + - name: Wait for the new version on npm + run: | + version=$(node -p "require('./package.json').version") + for i in $(seq 1 60); do + if curl -sf -o /dev/null "https://registry.npmjs.org/@openactive%2fdata-models/$version?nocache=$(date +%s)"; then + echo "$version is available on npm" + exit 0 + fi + echo "Attempt $i: $version not yet available, waiting" + sleep 30 + done + echo "$version was not available on npm within 30 minutes" + exit 1 dispatch: needs: publish