Skip to content
Open
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
32 changes: 32 additions & 0 deletions .github/workflows/sync-upstream-specs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Opens or refreshes one PR when a spec served by api.woosmap.com differs from upstream/.
name: Sync upstream specs
on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.4.0
- name: Download upstream specs
run: tools/sync-upstream-specs.sh
# A PAT, so that the branch push runs Build and Test on the PR.
- uses: peter-evans/create-pull-request@v8.1.1
with:
token: ${{ secrets.WOOSMAP_GH_ACCESS_TOKEN }}
add-paths: upstream
branch: chore/sync-upstream-specs
delete-branch: true
commit-message: "fix(spec): sync upstream OpenAPI specs"
title: "fix(spec): sync upstream OpenAPI specs"
body: |
## What?
Refreshes `upstream/` from `https://api.woosmap.com/<name>/openapi.json`.

## Why?
The merged spec only picks up upstream changes through this PR. Check the diff for contract changes that the SDKs need to follow before merging: merging publishes a patch release.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@ An OpenAPI specification for Woosmap Platform.
The repository makes use of [Bazel](https://bazel.build/) to generate outputs from the specification and sample
requests.

### Upstream specs

The APIs that publish their own spec at `https://api.woosmap.com/<name>/openapi.json` are vendored in `upstream/` and
merged into `dist/merged-woosmap-openapi3.json`. A weekly workflow refreshes them and opens a PR when they change.
`npm run sync:upstream` does the same refresh locally.

### Build and test

To be able to build the spec locally, you'll need a github personal access token (mandatory for merging with auto
generated spec such as Woosmap x What3Words).
Generate one with repository access here: <https://github.com/settings/tokens>.
To be able to generate responses, you'll need a woosmap public key and woosmap private key.
Once generated, it's convenient to add these environment variables in the file `.bazelrc.user` at the root of the
repository like this:

```bash
build --action_env GH_TOKEN=ghp_xxxxxxxxx
run --action_env WOOSMAP_PUBLIC_API_KEY=woos-xxxxxxxx
run --action_env WOOSMAP_PRIVATE_API_KEY=da4e8e73-xxxxx-xxxx
```
Expand Down
36 changes: 4 additions & 32 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,54 +1,40 @@
# Declares that this directory is the root of a Bazel workspace.
# See https://docs.bazel.build/versions/master/build-ref.html#workspace
workspace(
name = "openapi-specification",
)

# Install the nodejs "bootstrap" package
# This provides the basic tools for running and packaging nodejs programs in Bazel
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# Fetch Aspect's rules_js so we can install our npm dependencies
# v2.9.2 (still rules_js 2.x) ships a Node toolchain list that includes
# Node 22.x, required to register a Node >=22.12 toolchain below.
# Needs a Node toolchain list that includes Node >= 22.12, see node_version below.
http_archive(
name = "aspect_rules_js",
sha256 = "1774702556e1d0b83b7f5eb58ec95676afe6481c62596b53f5b96575bacccf73",
strip_prefix = "rules_js-2.9.2",
url = "https://github.com/aspect-build/rules_js/releases/download/v2.9.2/rules_js-v2.9.2.tar.gz",
)

# Fetch Aspect's rules_ts for TypeScript support
# v3.8.10 is the first release whose mirrored version list includes
# typescript 6.0.3 (see ts/private/versions.bzl), keeping the hermetic fetch.
# Oldest release whose mirrored version list includes typescript 6.0.3.
http_archive(
name = "aspect_rules_ts",
sha256 = "06a432998e3f0b4c1057926b3946b51057413c6ffcb19bbc5e2674191a061063",
strip_prefix = "rules_ts-3.8.10",
url = "https://github.com/aspect-build/rules_ts/releases/download/v3.8.10/rules_ts-v3.8.10.tar.gz",
)

# Register js dependencies
load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies")

rules_js_dependencies()

# Register TypeScript dependencies
load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies")

rules_ts_dependencies(
# This keeps the TypeScript version in-sync with the editor
ts_version_from = "//:package.json",
)

# Set up toolchains
load("@aspect_rules_js//js:toolchains.bzl", "rules_js_register_toolchains")

# Node 22.x (>=22.12) supersedes the EOL Node 18 default and enables require()
# of the now ESM-only remark/unified toolchain used by the doc generator.
# The ESM-only remark/unified toolchain needs require(esm), so Node >= 22.12.
rules_js_register_toolchains(node_version = "22.14.0")

# Set up npm
load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock")

npm_translate_lock(
Expand All @@ -75,17 +61,3 @@ http_archive(
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")

rules_pkg_dependencies()

[http_file(
name = name + "_openapi",
downloaded_file_path = name + ".json",
urls = ["https://api.woosmap.com/{}/openapi.json".format(name)],
) for name in [
"maps",
"what3words",
"indoor",
"transit",
"datasets",
"distance",
"localities",
]]
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"responses": "bazel run generator/responses:_responses_bin -- --output `pwd`/specification/responses",
"samples": "bazel run generator/requests:_samples_bin -- --output `pwd`/specification/snippets",
"publish:postman": "bazel run generator/postman:_postman_bin",
"sync:upstream": "tools/sync-upstream-specs.sh",
"test": "bazel test ...",
"watch": "ibazel build ...",
"watch:test": "ibazel test ..."
Expand Down
51 changes: 12 additions & 39 deletions rules/redocly_cli.bzl
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary", "js_test")
load("@aspect_rules_js//js:defs.bzl", "js_run_binary")

def bundle(name, entry, data = None, config = None, decorators = None, visibility = ["//visibility:public"], **kwargs):
"""Bundle OpenAPI files with redocly CLI."""
"""Bundles entry with redocly, dereferences it to JSON and derives the YAML."""
JSON_FILENAME = "{}.json".format(name)
YAML_FILENAME = "{}.yml".format(name)
RAW_TARGET = "{}_raw".format(name)
RAW_OUTPUT = "raw_{}.json".format(name) # Give unique name to avoid conflicts
RAW_OUTPUT = "raw_{}.json".format(name)

# Use the entry directly
all_srcs = []
if data:
all_srcs.extend(data)
Expand All @@ -19,22 +17,19 @@ def bundle(name, entry, data = None, config = None, decorators = None, visibilit

all_srcs.append(entry)

# Bundle raw output - use the redocly_cli binary from the root package
js_run_binary(
name = RAW_TARGET,
outs = [RAW_OUTPUT], # Use unique name
tool = "//:redocly_cli", # Use the binary from the npm package
outs = [RAW_OUTPUT],
tool = "//:redocly_cli",
args = [
"bundle",
"toBundle",
] + (["--config", "$(rootpath {})".format(config)] if config else []),
srcs = all_srcs,
env = {"DEBUG": "true"},
# Add a progress message that will be displayed during build
progress_message = "Bundling OpenAPI spec %{input} into %{output}",
)

# The JSON generation step needs to reference the output from RAW_TARGET properly
js_run_binary(
name = name + "_json",
outs = [JSON_FILENAME],
Expand All @@ -45,15 +40,12 @@ def bundle(name, entry, data = None, config = None, decorators = None, visibilit
"--output",
"$(rootpath {})".format(JSON_FILENAME),
],
# The spec sources are needed so dereference can resolve the
# example-value $refs (responses/requests) that redocly v2 leaves
# external in the bundle.
# dereference resolves the example $refs that redocly v2 leaves external, so it needs the sources.
srcs = [":" + RAW_TARGET] + all_srcs,
visibility = visibility,
env = {"BAZEL_BINDIR": "$(BINDIR)"},
)

# Generate YAML output from the JSON file
js_run_binary(
name = name + "_yaml",
outs = [YAML_FILENAME],
Expand Down Expand Up @@ -92,45 +84,27 @@ def validate(name, openapi_file = None, config = None, rules = None):
)

def bundle_external_specs(name, specs, main_spec = "//:woosmap-openapi3.json", config = None, plugins = None):
"""Downloads, bundles and joins multiple OpenAPI specs.

Args:
name: Target name for the final joined spec
specs: List of spec names to bundle
main_spec: Path to the main OpenAPI spec
config: Path to redocly config file
"""

# Copy external specs to a directory
copy_to_directory(
name = "downloaded_openapi_specs",
srcs = ["@{}_openapi//file".format(s) for s in specs],
out = "external_specs",
include_external_repositories = ["{}_openapi".format(s) for s in specs],
visibility = ["//visibility:public"],
)

# Bundle each spec
"""Joins main_spec with the specs vendored in //upstream, then applies the merge decorators."""
bundled_specs = []
for spec in specs:
bundle_name = "bundle_{}".format(spec)
spec_file = "//upstream:{}".format(spec)
bundled_specs.append(bundle_name)

js_run_binary(
name = bundle_name,
srcs = [":downloaded_openapi_specs"],
srcs = [spec_file],
outs = ["{}-bundled.json".format(spec)],
args = [
"bundle",
"$(rootpath :downloaded_openapi_specs)/file/{}.json".format(spec),
"$(rootpath {})".format(spec_file),
"--output",
"{}-bundled.json".format(spec),
"--remove-unused-components",
],
tool = "//:redocly_cli",
)

# Join specs
joined_target = name + "_joined"
joined_output = "joined-woosmap-openapi3.json"

Expand All @@ -151,14 +125,13 @@ def bundle_external_specs(name, specs, main_spec = "//:woosmap-openapi3.json", c
visibility = ["//visibility:public"],
)

# Apply decorators to the joined spec
final_srcs = [":" + joined_target]
if config:
final_srcs.append(config)
if plugins:
final_srcs.extend(plugins)
# Add specification files (includes snippets and responses) for inject-code-samples decorator

# The inject-code-samples decorator reads the snippets from the spec sources.
final_srcs.append("//specification:openapi3")

js_run_binary(
Expand Down
10 changes: 10 additions & 0 deletions tools/sync-upstream-specs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Refreshes each upstream/<name>.json from https://api.woosmap.com/<name>/openapi.json.
set -euo pipefail

cd "$(dirname "$0")/../upstream"
for spec in *.json; do
name="${spec%.json}"
curl -sSf --retry 3 "https://api.woosmap.com/${name}/openapi.json" | jq . > "${spec}.tmp"
mv "${spec}.tmp" "${spec}"
done
7 changes: 7 additions & 0 deletions upstream/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")

[copy_to_bin(
name = spec.removesuffix(".json"),
srcs = [spec],
visibility = ["//visibility:public"],
) for spec in glob(["*.json"])]
Loading
Loading