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
74 changes: 74 additions & 0 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Publish core and map packages

on:
release:
types: [published]

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for npm trusted publishing and provenance.
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org

# Trusted Publishing (OIDC) requires npm >= 11.5.1, which the runner's
# bundled npm is not guaranteed to be. Pin to a known-good version rather
# than `npm@latest`: that tag briefly shipped a build missing the bundled
# `sigstore` module, which breaks `npm publish --provenance`.
- name: Upgrade npm for Trusted Publishing
run: npm install -g npm@11.5.1

- run: npm ci

- name: Build the distributed bundles
run: npm run build -w @geolibre/core -w @geolibre/map

# The manifests in git point at TypeScript source so the monorepo needs no
# build step; npm consumers need `dist`. npm ignores entry fields nested
# under `publishConfig`, so hoist them here, just before publishing.
# @geolibre/core is prepared alongside @geolibre/map so the `"*"`
# dependency between them is pinned to this release's version.
- name: Rewrite the manifests for publishing
run: node scripts/prepare-npm-package.mjs packages/core packages/map

# Each package is checked against its own version: the two move in
# lockstep today, but reading core's version for map would silently skip
# a map release (or attempt one npm has already seen) the moment they
# diverge.
- id: version
run: |
for pkg in core map; do
version=$(node -p "require('./packages/$pkg/package.json').version")
if npm view "@geolibre/$pkg@$version" version >/dev/null 2>&1; then
echo "$pkg=false" >> "$GITHUB_OUTPUT"
else
echo "$pkg=true" >> "$GITHUB_OUTPUT"
fi
done
Comment thread
giswqs marked this conversation as resolved.

# No NODE_AUTH_TOKEN: npm authenticates via the OIDC id-token from the
# trusted publisher configured on each package (opengeos/GeoLibre, this
# workflow file) and attaches build provenance. npm has no PyPI-style
# pending publisher, so a package has to exist before that connection can
# be made: the first version is published from a maintainer's machine
# once, then every later release goes through here.
- name: Publish @geolibre/core
if: steps.version.outputs.core == 'true'
run: npm publish -w @geolibre/core --provenance --access public

# Published after core so a consumer installing @geolibre/map never
# resolves a pinned @geolibre/core that is not on the registry yet.
- name: Publish @geolibre/map
if: steps.version.outputs.map == 'true'
run: npm publish -w @geolibre/map --provenance --access public
24 changes: 23 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ The app is **store-driven**. `@geolibre/core` holds the Zustand store, domain ty

Rendering is MapLibre GL JS in the webview, with **deck.gl** for raster/point-cloud/3D overlays.

**Packages:** `@geolibre/core` (types, project format, store) · `@geolibre/map` (MapLibre lifecycle + layer sync) · `@geolibre/ui` (shadcn-style primitives) · `@geolibre/processing` (client-side algorithm registry) · `@geolibre/plugins` (plugin interface + built-in plugins) · `@geolibre/embed` (typed iframe embed client, the one package published to npm — `.github/workflows/publish-embed.yml` publishes it on each GitHub Release, skipping a version already there) · `geolibre-desktop` (shell layout, Tauri I/O, composition).
**Packages:** `@geolibre/core` (types, project format, store) · `@geolibre/map` (MapLibre lifecycle + layer sync) · `@geolibre/ui` (shadcn-style primitives) · `@geolibre/processing` (client-side algorithm registry) · `@geolibre/plugins` (plugin interface + built-in plugins) · `@geolibre/embed` (typed iframe embed client — `.github/workflows/publish-embed.yml` publishes it on each GitHub Release, skipping a version already there) · `geolibre-desktop` (shell layout, Tauri I/O, composition).

**Plugins:** Built-in plugins live in `packages/plugins/src/plugins/`, are exported from that package's `index.ts`, and registered in `apps/geolibre-desktop/src/hooks/usePlugins.ts`. External plugins load from zips or a `plugin.json` manifest; bundled drop-ins under `apps/geolibre-desktop/public/plugins/<id>/` bake into both web and desktop builds. See `docs/plugin-api.md`.

Expand All @@ -123,6 +123,28 @@ The browser build proxies the sidecar at `/sidecar` (same-origin, no CORS); conf
## Conventions

- Never commit directly to `main`; branch and open a PR.
- **`@geolibre/core` and `@geolibre/map` are published to npm** by
`.github/workflows/publish-packages.yml` on each GitHub Release, alongside
`@geolibre/embed`. Their checked-in `main`/`types`/`exports` point at
TypeScript **source**, because that is how the monorepo consumes them: Vite,
`tsc` and tsx all resolve `./src/index.ts` through the package's own
`exports`, so `npm run dev` and `node --import tsx --test tests/<name>.test.ts`
need no build step. The npm tarball ships `dist` instead, and npm cannot
express that split on its own: unlike pnpm and Yarn it deliberately **ignores
entry fields nested under `publishConfig`** (npm/cli#7586), so a manifest that
only states its dist entries there publishes `./src/index.ts` to consumers who
never receive `src`. The published entries therefore live under
`publishConfig`, and `scripts/prepare-npm-package.mjs` hoists them (and pins
the `"*"` `@geolibre/core` dependency to the release version) just before
`npm publish`. Point those top-level fields at `dist` and every frontend test
that imports a `@geolibre/map` subpath fails with `ERR_MODULE_NOT_FOUND`,
because `dist` is gitignored and nothing builds it before the suite.
`tests/prepare-npm-package.test.ts` guards both halves, including that each
published path is one the package's own `tsdown` entries actually emit
(`--format esm --dts` writes `<entry>.mjs` and `<entry>.d.mts`, **not**
`.d.ts`). The release workflow does build both packages, but a green build
proves only that the bundles were written, not that every path the manifest
publishes names one of them, so nothing else would notice that drift.
- **`backend/geolibre_server/uv.lock` is committed** (the root `.gitignore` ignores `uv.lock` everywhere else and negates it for this one path). That project is bundled into the desktop installers and launched with `uv run --frozen --project <resource dir>` from `src-tauri/src/lib.rs` — a directory the user cannot write (`C:\Program Files\…`, `/usr/lib/GeoLibre Desktop/…`). Ship it lockless and uv resolves, then tries to _write_ `uv.lock` there, fails with "Permission denied" and exits 2 — which reaches the user as "Jupyter server exited before it was ready (exit code: 2)" with the cause invisible. So: any edit to that `pyproject.toml`'s dependencies must land with a refreshed lock (`uv lock --project backend/geolibre_server`). CI's "Check the bundled sidecar lockfile is in sync" step (`uv lock --check`) fails if they drift.
- Tauri CSP allowlists tile/style hosts (OpenFreeMap, CARTO) — new external map/tile hosts must be added there.
- Map/tile-host CORS for selected release assets is handled by a dev-server raster proxy.
Expand Down
Loading
Loading