Skip to content
Merged
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
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ that depends on another. Build first and it goes away — nothing is broken.
| Path | What | How it's tested |
| --- | --- | --- |
| `packages/validate` | Event/Feed validation against the OTE JSON Schema | vitest + fixtures |
| `packages/migrate` | Moves a document to a newer spec version + D032 warnings | vitest + fixtures |
| `packages/build-feed` | Assembles `events/*.json` + `ote.config.json` → `feed.json` | vitest + fixtures |
| `packages/export-ics` / `export-rss` / `export-jsonld` | `feed.json` → ICS / RSS / schema.org JSON-LD | vitest + fixtures |
| `apps/editor` | Static web editor (form → event JSON → issue/PR links) | vitest for `src/lib/`; UI by hand |
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Design rationale lives in [DESIGN.md](DESIGN.md); the spec lives in

| Package | What it does |
| --- | --- |
| [`@opentechevents/validate`](packages/validate/) | Validates OTE Event/Feed documents against the vendored v0.4 JSON Schema. |
| [`@opentechevents/validate`](packages/validate/) | Validates OTE Event/Feed documents against the version each one declares. Every published spec version is embedded. |
| [`@opentechevents/migrate`](packages/migrate/) | Moves a document to a newer spec version, and warns where D032's multi-part pricing change silently altered what it means. |
| [`@opentechevents/export-ics`](packages/export-ics/) | Valid OTE Feed → iCalendar (RFC 5545). |
| [`@opentechevents/export-rss`](packages/export-rss/) | Valid OTE Feed → RSS 2.0. |
| [`@opentechevents/export-jsonld`](packages/export-jsonld/) | Valid OTE Feed/Event → schema.org `Event` JSON-LD (SEO structured data). |
Expand Down
35 changes: 35 additions & 0 deletions packages/migrate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Changelog

All notable changes to `@opentechevents/migrate` are documented here.

## 0.1.0

First release. Closes the last open part of #97.

- **`migrateDocument(json, options?)` and `ote-migrate`** move an OTE feed or
standalone event to a newer spec version. The JSON change is `specVersion`
and nothing else — every published release so far has been additive — but
the result is validated against the target version rather than assumed, so
a future release that breaks that pattern fails here instead of shipping.
- **D032 warnings, which are the reason this is a package and not a `sed`.**
Before 0.5 a part of a multi-part event carried its own complete `offers`;
from 0.5 an unscoped offer is read as `admission` and a surcharge needs
`"scope": "add-on"` plus `partOf.offers`. The old document stays valid
while quietly under-reporting what attending costs, so the migration says
which events need a human and what the question is. It never rewrites
pricing: whether a number is the whole price or an extra is a fact about
the event, not about the JSON.
- Warnings never change the exit code. They are decisions, not faults.
- **Documents outside the support window migrate.** Validation here is
against the version the document declares, deliberately bypassing the
support policy that `validateDocument` applies — a feed on an unsupported
release is precisely the one that has to run this, and a migrator that
refused it would be useless to the only people who need it. A registered
adopter was on 0.3 when 0.3 left the window.
- Refuses, rather than guessing: a missing or never-published `specVersion`,
an unpublished target, a **downgrade** (dropping fields a newer version
added is silent data loss), and input that is already invalid at the
version it claims.
- Output is written with two-space indent and a trailing newline — what OTE
documents in this ecosystem already use, so migrating one does not show up
as a whole-file reformat in the next diff.
123 changes: 123 additions & 0 deletions packages/migrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# @opentechevents/migrate

Moves an OTE document to a newer spec version — and tells you the one thing a
find-and-replace gets wrong.

```sh
npx @opentechevents/migrate feed.json --write
```

## Why this is not `sed`

What it does to your JSON really is small: it rewrites `specVersion`. Every
published OTE release so far has been additive, so no valid document has ever
needed a structural change to move up. `migrateDocument` checks that rather
than assuming it — the output is validated against the target version before
you get it back.

The reason this is a package is **D032**, the multi-part pricing change in
0.5.

Before 0.5, a part of a multi-part event carried its own complete `offers`.
That was the only way to price it; there was no way to say *"this workshop
costs 60 on top of the conference ticket"*. From 0.5 there is: the set's
shared admission goes in `partOf.offers`, and a surcharge is an offer with
`"scope": "add-on"`.

The trap is that the old document stays **valid**. An offer with no `scope` is
read as `admission` — a complete way to attend that part. So a workshop that
really costs 240 + 60 goes on publishing, validly and machine-readably, that
it costs 60. Nothing can flag that automatically: whether the 60 is the whole
price or an extra is a fact about your event, not about the JSON.

So `ote-migrate` asks you:

```text
✓ feed.json — OTE 0.4.0 → 0.6.0

! events[1].offers (D032)
This event ("Taller de testing en Python") is part of a multi-part set and
carries its own offers. From 0.5 an offer without a "scope" is read as
"admission" — a complete way to attend this part. If any of these prices is
really a surcharge on top of the set's ticket, mark it "scope": "add-on"
and put the shared admission in partOf.offers, or the feed will
under-report what attending costs.

3 things to check by hand. The document is valid either way — these are
about what it means.
```

Warnings never change the exit code. They are decisions, not faults.

## It works on versions that are out of support

The support window covers the last three minors. A document older than that
gets an error from `ote-validate` — correctly, because a consumer built for
the window cannot read it:

```sh
$ ote-validate feed-0.3.json
✗ feed-0.3.json (feed) — OTE 0.3.0
specVersion: OTE Spec 0.3.0 is outside the support window … migrate to 0.6.0.
```

That document is exactly the one that needs migrating, so this tool validates
against the version the document declares and ignores the window:

```sh
$ ote-migrate feed-0.3.json --write
✓ feed-0.3.json — OTE 0.3.0 → 0.6.0
```

## CLI

```text
Usage: ote-migrate <document.json> [output.json] [options]

--to <version> Target spec version (default: the newest published one)
--kind <k> feed | event (default: inferred from the document)
--write Rewrite the input file in place
--versions List the published versions and exit
```

With no output path and no `--write`, the document goes to stdout and
everything else to stderr, so it pipes cleanly:

```sh
ote-migrate feed.json | jq .specVersion
```

Exit codes: `0` migrated · `1` cannot migrate (unplaceable version, a
downgrade, or input that is invalid at the version it claims) · `2` usage or
I/O error.

## Library

```ts
import { migrateDocument } from "@opentechevents/migrate";

const { document, from, to, warnings } = await migrateDocument(feed, {
to: "0.6.0",
});
```

`migrateDocument(json, options?)` returns `{ kind, from, to, changed,
document, warnings }`. The input is never mutated. It throws `MigrationError`
when the migration cannot be attempted:

- the document declares no `specVersion`, or one that was never published —
there are no rules to migrate it from;
- the target is not a published version;
- the target is **older** than the declared version. Migrating downwards means
dropping fields a newer version added, and silently losing data somebody
wrote on purpose is worse than refusing;
- the input is not valid at the version it claims. Migrating an invalid
document produces an invalid one wearing a newer label, and a later reader
has no way to tell the damage came from here.

## What it will not do

It does not rewrite your pricing, invent `partOf.offers`, or guess a `scope`.
The repo-wide rule that connectors never invent data applies here too, and
D032 is the case where guessing would be most tempting and most expensive —
a wrong price is worse than an absent one.
Loading
Loading