Metadata-Version: 2.5
Name: commonmeta-schema
Version: 1.0.9
Summary: Commonmeta JSON Schemas and conformance fixtures
Project-URL: Homepage, https://github.com/front-matter/commonmeta-schema
Project-URL: Repository, https://github.com/front-matter/commonmeta-schema
Project-URL: Issues, https://github.com/front-matter/commonmeta-schema/issues
Author: Front Matter
License-Expression: MIT
License-File: LICENSE
Keywords: commonmeta,fixtures,json-schema,metadata,schema
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Text Processing
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# commonmeta-schema

Language-neutral JSON Schema definitions and conformance fixtures for
[Commonmeta](https://commonmeta.org), the scholarly metadata interchange format.

This repository is the shared source of truth consumed by the following Commonmeta
implementations:

- [commonmeta-rs](https://github.com/front-matter/commonmeta-rs) (Rust)
- [commonmeta-py](https://github.com/front-matter/commonmeta-py) (Python)

Keeping the schema and the golden fixtures in one place lets every
implementation validate against the same contract and run the same
cross-format conformance tests.

## Layout

```
schemas/
  commonmeta_v1.0.json         # the Commonmeta JSON Schema (current)
fixtures/
  commonmeta/                  # canonical Commonmeta records (round-trip + expected output)
  <format>/                    # input fixtures in a given source format
  <format>_out/                # expected writer output (Commonmeta -> format)
  <format>_commonmeta/         # expected reader output for non-JSON inputs
  utils/                       # shared utility fixtures for helpers outside format conversion
```

## Schema versions

Schemas are versioned by filename. The current version is `commonmeta_v1.0.json`,
and the `schema_version` field of a Commonmeta record is the URL
`https://commonmeta.org/commonmeta_v1.0.json`.

The packages export only `v1.0`. New versions are added alongside existing files
rather than replacing them, so implementations can pin a version.

## Fixture conventions

The conformance harness in each implementation follows these naming rules
(a missing pair is skipped, so partial coverage is fine):

| Test kind            | Input                                  | Expected                                    |
|----------------------|----------------------------------------|---------------------------------------------|
| Round-trip           | `fixtures/commonmeta/<name>.json`      | itself (re-serialized, semantically equal)  |
| Reader (JSON input)  | `fixtures/<format>/<name>.json`        | `fixtures/commonmeta/<name>.json`           |
| Reader (text input)  | `fixtures/<format>/<name>.<ext>`       | `fixtures/<format>_commonmeta/<name>.json`  |
| Writer               | `fixtures/commonmeta/<name>.json`      | `fixtures/<format>_out/<name>.<ext>`        |

Formats currently covered: `crossref`, `crossref_xml`, `datacite`,
`datacite_xml`, `schemaorg`, `csl`, `bibtex`, `cff`, `ris`,
`inveniordm`, `codemeta`, `orcid`, `orcid_xml`, `openalex_source`.
Writer goldens exist for `bibtex`, `crossref`, `csl` and `schemaorg`.

### CSL 1.0.2

The `csl` fixtures use CSL 1.0.2 variables, mapped as follows:

| CSL variable            | Commonmeta                                      |
|-------------------------|-------------------------------------------------|
| `available-date`        | `dates.available`                               |
| `container-title-short` | `container.additional_titles`                   |
| `event-title`           | `container.event`                               |
| `event-place`           | `container.location`                            |
| `publisher-place`       | `publisher.geo_locations[].place`               |
| `collection-title`      | `container.title` of a `BookSeries` container   |
| `edition`               | `edition`                                       |
| `number`                | `number`                                        |
| `genre`                 | `additional_type`                               |

### OpenAlex scope

The `openalex_source` fixtures cover OpenAlex **sources/containers** (for
example journal, repository, and blog containers), not OpenAlex works.

- `fixtures/openalex_source/*.json` are OpenAlex source records (`S...` IDs).
- `fixtures/commonmeta/openalex_source_*.json` are the expected Commonmeta
  container entities.

OpenAlex works should be added under a separate format namespace (for example
`openalex_work`) to keep reader and writer behavior unambiguous.

Most fixtures are `work` entities; `orcid` and `orcid_xml` cover `person`
instead. Both read a full ORCID record — with employments and educations as
affiliations — from two different sources:

- `orcid/<id>.json` is the ORCID REST API `/record` response (JSON). Because it
  carries the full person, its commonmeta output also has `description` and
  `urls`. → `commonmeta/<id>.json`
- `orcid_xml/<id>.xml` is the ORCID Public Data File record (XML), a summary
  that omits biography and researcher URLs. → `orcid_xml_commonmeta/<id>.json`

Both fixtures trim the record's works list to the 10 most recent (the readers
convert person identity and affiliations, not works).

### Semantic comparison

Fixtures are compared as parsed JSON trees, not as strings, using an
omitempty-aware and numeric-aware diff: key order and whitespace are
irrelevant, an absent field equals an empty/zero/null value, and `52`
equals `52.0`. This keeps hand-authored fixtures robust across
implementations.

### Utility fixtures

`fixtures/utils/` is reserved for shared cross-implementation test data for
helper functions outside reader/writer format conversion. The first use is DOI
helper coverage for `encode_doi` and `decode_doi`. These fixtures are not
reader/writer conformance cases; they exist so `commonmeta-py` and
`commonmeta-rs` can assert the same normalization, checksum, and error
handling behavior from one canonical source.

## Using this repository

Each implementation vendors (copies) the schema and fixtures it needs from
here. Update the canonical files in this repository first, then sync them
into `commonmeta-rs` and `commonmeta-py`.

## Packaging and publishing

This repository can be published as both:

- a Python package on PyPI (`commonmeta-schema`)
- a Rust crate on crates.io (`commonmeta-schema`)
- a JavaScript package on npm (`commonmeta-schema`)

Publishing is done explicitly via CLI (no automated release pipeline in this repo).

The packages are released as `1.0.9`; both ship the `v1.0` schema:

- PyPI uses PEP 440 form: `1.0.9`
- crates.io uses SemVer form: `1.0.9`
- npm uses SemVer form: `1.0.9`

`schemas/commonmeta_v1.0.json` is edited directly. `scripts/sync_versions.py` is
the single entry point for preparing a release. It does three things:

1. Mirrors the canonical `schemas/` and `fixtures/` into `rust/schemas/` and
   `rust/fixtures/` (the crate can only package files under `rust/`).
2. Syncs `rust/Cargo.toml`'s version with `pyproject.toml`'s.
3. Syncs `package.json`'s version with `pyproject.toml`'s.

```bash
python scripts/sync_versions.py
```

To verify sync in CI/local checks without changing files:

```bash
python scripts/sync_versions.py --check
```

Recommended pre-release checks:

```bash
python scripts/sync_versions.py --check
uv build
cargo test --manifest-path rust/Cargo.toml
cargo package --manifest-path rust/Cargo.toml
npm pack --dry-run
```

### JavaScript / TypeScript (npm)

Install:

```bash
npm install commonmeta-schema
```

Read packaged assets by path:

```js
import { readFileSync } from "node:fs";
import { fixturesPath, schemaPath } from "commonmeta-schema";

const schema = JSON.parse(readFileSync(schemaPath("1.0"), "utf8"));
const fixture = JSON.parse(
  readFileSync(`${fixturesPath("commonmeta")}/journal_article.json`, "utf8")
);
```

Or import JSON files directly from subpath exports:

```js
import schema from "commonmeta-schema/schemas/commonmeta_v1.0.json" with { type: "json" };
```

The helper API mirrors the Python package and exports `packageRoot`,
`schemaPath(version)`, `fixturesPath(formatName)`, and
`availableSchemaVersions()`.

### Python (PyPI) via `uv publish`

1. Ensure you are authenticated for PyPI (recommended: trusted publisher or API token).
2. Build distribution artifacts:

  ```bash
  uv build
  ```

3. Optionally validate the build metadata:

  ```bash
  uvx twine check dist/*
  ```

4. Publish to PyPI:

  ```bash
  uv publish
  ```

### Rust (crates.io) via `cargo publish`

1. Log in once with a crates.io token:

  ```bash
  cargo login
  ```

2. Validate package contents:

  ```bash
  cargo package --manifest-path rust/Cargo.toml --allow-dirty
  ```

3. Publish:

  ```bash
  cargo publish --manifest-path rust/Cargo.toml
  ```

### JavaScript / TypeScript (npm) via `npm publish`

1. Authenticate with npm (once per environment):

  ```bash
  npm login
  npm whoami
  ```

2. Validate package contents:

  ```bash
  npm pack --dry-run
  ```

3. Publish the version from `package.json`:

  ```bash
  npm publish
  ```

The package is public and its version must not already exist on npm. Verify the
published version after npm completes:

```bash
npm view commonmeta-schema version
```

### Recommended release order

1. Publish `1.0.9` to PyPI with `uv publish`.
2. Publish `1.0.9` to crates.io with `cargo publish`.
3. Publish `1.0.9` to npm with `npm publish`.
4. Create a git tag `v1.0.9` after all uploads succeed.

### Copy-paste release run

Run this from the repository root:

```bash
set -euo pipefail

# 1) Mirror assets into rust/, sync the crate and npm versions
python scripts/sync_versions.py

# 2) Fail the release if anything is still out of sync
python scripts/sync_versions.py --check

# 3) Validate both package builds
uv build
cargo test --manifest-path rust/Cargo.toml
cargo package --manifest-path rust/Cargo.toml
npm pack --dry-run

# 4) Publish Python package
uv publish

# 5) Publish Rust crate
cargo publish --manifest-path rust/Cargo.toml

# 6) Publish npm package
npm publish

# 7) Tag release (replace with the release version)
git tag v1.0.9
git push origin v1.0.9
```

## License

[MIT](LICENSE) © 2026 Front Matter
