Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.
Draft
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
255 changes: 255 additions & 0 deletions .github/workflows/publish-scope-catalog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
name: Publish Scope Catalog

on:
workflow_dispatch:
inputs:
confirmInitialPublish:
description: "Confirm Callum approved the first @opendatalabs/scope-catalog@1.0.0 publish"
required: true
type: boolean
default: false
push:
branches: [main]
paths:
- ".github/workflows/publish-scope-catalog.yml"
- "connectors/**/*-playwright.json"
- "connectors/**/schemas/*.json"
- "packages/scope-catalog/**"
- "registry.json"
- "schemas/manifest.schema.json"
- "schemas/scope-catalog.schema.json"
- "schemas/web-scope-capabilities.schema.json"
- "scope-catalog.json"
- "scopes/web-capabilities.json"
- "scripts/build-scope-catalog-package.mjs"
- "scripts/check-scope-catalog-package.mjs"
- "scripts/diff-scope-catalog-package.mjs"
- "scripts/generate-scope-catalog.mjs"
- "scripts/generate-scope-catalog.test.mjs"
- "scripts/scope-catalog-package.test.mjs"
- "package.json"
- "package-lock.json"

permissions:
contents: read
id-token: write

concurrency:
group: publish-scope-catalog
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "24.4.0"
registry-url: https://registry.npmjs.org

- name: Pin trusted-publishing-compatible npm
run: npm install --global npm@11.5.1

- name: Install dependencies
run: npm ci

- name: Validate BUI-705 manifest and schema inputs
run: |
node scripts/validate-manifests.mjs
node scripts/normalize-manifests.mjs --check
node scripts/validate-scope-schemas.mjs

- name: Test BUI-705 catalog contract
run: npm run scope-catalog:test

- name: Check BUI-705 generated catalog
run: npm run scope-catalog:check

- name: Resolve exact previous package
id: previous
env:
PACKAGE_NAME: "@opendatalabs/scope-catalog"
run: |
set -euo pipefail
previous_dir="$RUNNER_TEMP/scope-catalog-previous"
mkdir -p "$previous_dir/packed" "$previous_dir/unpacked"

set +e
npm view "$PACKAGE_NAME" version --json > "$previous_dir/version.json" 2> "$previous_dir/view-error.log"
view_status=$?
set -e

if [ "$view_status" -eq 0 ]; then
previous_version="$(node -p "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$previous_dir/version.json")"
npm pack "$PACKAGE_NAME@$previous_version" --pack-destination "$previous_dir/packed" --json > "$previous_dir/pack.json"
tarball="$(node -p "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))[0].filename" "$previous_dir/pack.json")"
tar -xzf "$previous_dir/packed/$tarball" -C "$previous_dir/unpacked"
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "version=$previous_version" >> "$GITHUB_OUTPUT"
echo "root=$previous_dir/unpacked/package" >> "$GITHUB_OUTPUT"
elif grep -q "E404" "$previous_dir/view-error.log"; then
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "version=" >> "$GITHUB_OUTPUT"
echo "root=" >> "$GITHUB_OUTPUT"
else
cat "$previous_dir/view-error.log" >&2
exit "$view_status"
fi

- name: Build package and semantic release metadata
env:
PACKAGE_EXISTS: ${{ steps.previous.outputs.exists }}
PREVIOUS_PACKAGE_ROOT: ${{ steps.previous.outputs.root }}
run: |
if [ "$PACKAGE_EXISTS" = "true" ]; then
npm run scope-catalog-package:build -- --previous-package "$PREVIOUS_PACKAGE_ROOT"
else
npm run scope-catalog-package:build
fi

- name: Check deterministic package
env:
PACKAGE_EXISTS: ${{ steps.previous.outputs.exists }}
PREVIOUS_PACKAGE_ROOT: ${{ steps.previous.outputs.root }}
run: |
if [ "$PACKAGE_EXISTS" = "true" ]; then
npm run scope-catalog-package:check -- --previous-package "$PREVIOUS_PACKAGE_ROOT"
else
npm run scope-catalog-package:check
fi

- name: Test package contract and release gates
run: npm run scope-catalog-package:test

- name: Decide whether publication is allowed
id: release
env:
EVENT_NAME: ${{ github.event_name }}
INITIAL_CONFIRMED: ${{ github.event_name == 'workflow_dispatch' && inputs.confirmInitialPublish == true }}
PACKAGE_EXISTS: ${{ steps.previous.outputs.exists }}
run: |
set -euo pipefail
decision="$(node --input-type=module <<'NODE'
import { readFileSync } from "node:fs";
import { decideScopeCatalogPublication } from "./scripts/diff-scope-catalog-package.mjs";

const release = JSON.parse(
readFileSync("./packages/scope-catalog/release.json", "utf8"),
);
const decision = decideScopeCatalogPublication({
eventName: process.env.EVENT_NAME,
packageExists: process.env.PACKAGE_EXISTS === "true",
initialConfirmed: process.env.INITIAL_CONFIRMED === "true",
release,
});
process.stdout.write(JSON.stringify({ ...decision, release }));
NODE
)"
impact="$(node -p "JSON.parse(process.argv[1]).release.impact" "$decision")"
version="$(node -p "JSON.parse(process.argv[1]).release.currentVersion" "$decision")"
should_publish="$(node -p "JSON.parse(process.argv[1]).shouldPublish" "$decision")"
authentication="$(node -p "JSON.parse(process.argv[1]).authentication" "$decision")"
reason="$(node -p "JSON.parse(process.argv[1]).reason" "$decision")"

echo "impact=$impact" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "should_publish=$should_publish" >> "$GITHUB_OUTPUT"
echo "authentication=$authentication" >> "$GITHUB_OUTPUT"
echo "reason=$reason" >> "$GITHUB_OUTPUT"

{
echo "### Scope catalog package"
echo "- Version: \`$version\`"
echo "- Impact: \`$impact\`"
echo "- Publish: \`$should_publish\`"
echo "- Authentication: \`$authentication\`"
echo "- Reason: $reason"
if [ "$authentication" = "bootstrap-token" ]; then
echo "- After 1.0.0: configure npm trusted publishing, delete the NPM_TOKEN GitHub secret, and revoke the bootstrap token."
elif [ "$authentication" = "oidc-trusted-publishing" ]; then
echo "- NPM_TOKEN is not passed; npm trusted publishing must already be configured for this workflow."
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Pack exact package contents
id: pack
run: |
set -euo pipefail
pack_dir="$RUNNER_TEMP/scope-catalog-pack"
mkdir -p "$pack_dir"
npm pack ./packages/scope-catalog --pack-destination "$pack_dir" --json > "$pack_dir/pack.json"
filename="$(node -p "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))[0].filename" "$pack_dir/pack.json")"
echo "tarball=$pack_dir/$filename" >> "$GITHUB_OUTPUT"

- name: Smoke-test every package export
env:
TARBALL: ${{ steps.pack.outputs.tarball }}
run: |
set -euo pipefail
consumer="$RUNNER_TEMP/scope-catalog-consumer"
mkdir -p "$consumer"
cd "$consumer"
npm init -y >/dev/null
npm install --ignore-scripts "$TARBALL"
node --input-type=module <<'NODE'
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const catalog = require("@opendatalabs/scope-catalog/scope-catalog.json");
require("@opendatalabs/scope-catalog/schemas/scope-catalog.schema.json");
require("@opendatalabs/scope-catalog/release.json");
require("@opendatalabs/scope-catalog/package.json");
for (const scope of catalog.scopes) {
require(`@opendatalabs/scope-catalog/${scope.schema.path}`);
}
console.log(`Imported all required exports and ${catalog.scopes.length} payload schemas.`);
NODE

- name: Publish initial package with bootstrap token
if: >-
steps.release.outputs.should_publish == 'true' &&
steps.release.outputs.authentication == 'bootstrap-token' &&
steps.previous.outputs.exists != 'true' &&
github.event_name == 'workflow_dispatch' &&
inputs.confirmInitialPublish == true
env:
CHECKED_OUT_SHA: ${{ github.sha }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
current_main_sha="$(git ls-remote --exit-code origin refs/heads/main | awk '{ print $1 }')"
if [ "$CHECKED_OUT_SHA" != "$current_main_sha" ]; then
{
echo "### Scope catalog publication skipped"
echo "- Reason: this run no longer represents current main."
echo "- Checked-out SHA: \`$CHECKED_OUT_SHA\`"
echo "- Current main SHA: \`$current_main_sha\`"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

npm publish "${{ steps.pack.outputs.tarball }}" --access public --provenance

- name: Publish later package with trusted publishing
if: >-
steps.release.outputs.should_publish == 'true' &&
steps.release.outputs.authentication == 'oidc-trusted-publishing' &&
steps.previous.outputs.exists == 'true'
env:
CHECKED_OUT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
current_main_sha="$(git ls-remote --exit-code origin refs/heads/main | awk '{ print $1 }')"
if [ "$CHECKED_OUT_SHA" != "$current_main_sha" ]; then
{
echo "### Scope catalog publication skipped"
echo "- Reason: this run no longer represents current main."
echo "- Checked-out SHA: \`$CHECKED_OUT_SHA\`"
echo "- Current main SHA: \`$current_main_sha\`"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

npm publish "${{ steps.pack.outputs.tarball }}" --access public --provenance
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"scope-catalog:generate": "node ./scripts/generate-scope-catalog.mjs",
"scope-catalog:check": "node ./scripts/generate-scope-catalog.mjs --check",
"scope-catalog:test": "node --test ./scripts/generate-scope-catalog.test.mjs",
"scope-catalog-package:build": "node ./scripts/build-scope-catalog-package.mjs",
"scope-catalog-package:check": "node ./scripts/check-scope-catalog-package.mjs",
"scope-catalog-package:diff": "node ./scripts/diff-scope-catalog-package.mjs",
"scope-catalog-package:test": "node --test ./scripts/scope-catalog-package.test.mjs",
"release:sign": "node ./scripts/sign-release-assets.mjs"
},
"devDependencies": {
Expand Down
7 changes: 7 additions & 0 deletions packages/scope-catalog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by `npm run scope-catalog-package:build`.
/CHANGELOG.md
/connectors/
/package.json
/release.json
/schemas/
/scope-catalog.json
43 changes: 43 additions & 0 deletions packages/scope-catalog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# `@opendatalabs/scope-catalog`

Versioned, JSON-first distribution of Vana's public source and scope contract.

Install an exact version:

```sh
npm install --save-exact @opendatalabs/scope-catalog@1.0.0
```

Import the catalog and release metadata:

```js
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const catalog = require("@opendatalabs/scope-catalog/scope-catalog.json");
const release = require("@opendatalabs/scope-catalog/release.json");
```

Each `scope.schema.path` is relative to the package root and is exported at the same package subpath. For example, resolve `connectors/github/schemas/github.profile.json` as:

```js
const schema = require(
"@opendatalabs/scope-catalog/connectors/github/schemas/github.profile.json",
);
```

`catalogVersion` versions the catalog JSON format. The npm package version independently versions changes to the published public contract.

## Exports

- `@opendatalabs/scope-catalog/scope-catalog.json`
- `@opendatalabs/scope-catalog/schemas/scope-catalog.schema.json`
- `@opendatalabs/scope-catalog/connectors/*`
- `@opendatalabs/scope-catalog/release.json`
- `@opendatalabs/scope-catalog/package.json`

## Release metadata

`release.json` records the current and previous package versions and contract fingerprints, added and removed `[sourceId, scopeId]` pairs, per-pair old/current field changes, and the selected `major`, `minor`, `patch`, or `none` impact.

The package supports Node.js 20 or later and npm-compatible package managers. The Dependabot dependency name is `@opendatalabs/scope-catalog`.
32 changes: 32 additions & 0 deletions packages/scope-catalog/package.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@opendatalabs/scope-catalog",
"description": "Versioned public Vana source and scope catalog",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/vana-com/data-connectors.git",
"directory": "packages/scope-catalog"
},
"engines": {
"node": ">=20"
},
"files": [
"CHANGELOG.md",
"README.md",
"connectors",
"release.json",
"schemas",
"scope-catalog.json"
],
"exports": {
"./scope-catalog.json": "./scope-catalog.json",
"./schemas/scope-catalog.schema.json": "./schemas/scope-catalog.schema.json",
"./connectors/*": "./connectors/*",
"./release.json": "./release.json",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}
Loading
Loading