From 66ab161a55ee8fdd7346f85db1c6acc527d47dad Mon Sep 17 00:00:00 2001 From: Arechi Date: Thu, 30 Apr 2026 13:32:03 +0200 Subject: [PATCH 1/7] build: convert TypeScript package to ESM-only Switches the npm package from CommonJS to ESM-only output. - tsconfig.json: module/moduleResolution NodeNext, target ES2022 - package.json: adds "type": "module" and "exports" map; drops "main" - CI Generate: adds esModuleInterop=true and importSuffix=.js so ts-proto emits relative imports with .js extensions for NodeNext Breaking change for CommonJS consumers (require() is blocked by the exports map); a major version bump should accompany this before publishing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci-typescript.yaml | 4 +++- package.json | 10 ++++++++-- tsconfig.json | 5 +++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-typescript.yaml b/.github/workflows/ci-typescript.yaml index 1c174753..9d382b1a 100644 --- a/.github/workflows/ci-typescript.yaml +++ b/.github/workflows/ci-typescript.yaml @@ -45,7 +45,9 @@ jobs: --ts_proto_out=./out \ --ts_proto_opt=outputIndex=true \ --ts_proto_opt=globalThisPolyfill=true \ - --ts_proto_opt=useExactTypes=false + --ts_proto_opt=useExactTypes=false \ + --ts_proto_opt=esModuleInterop=true \ + --ts_proto_opt=importSuffix=.js - name: Compile run: pnpm run build diff --git a/package.json b/package.json index aa763b88..e5dbd325 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,14 @@ "type": "git", "url": "https://github.com/BluEye-Robotics/ProtocolDefinitions.git" }, - "main": "dist/index.js", - "types": "dist/index.d.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "types": "./dist/index.d.ts", "files": [ "dist" ], diff --git a/tsconfig.json b/tsconfig.json index fc30754e..54339cbc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "target": "ES2020", - "module": "CommonJS", + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", "declaration": true, "outDir": "dist", "strict": true, From 8fc95c5b2ef5092a1e4fd9c0ca928915c01a8a01 Mon Sep 17 00:00:00 2001 From: Arechi Date: Tue, 18 Aug 2026 21:00:22 +0200 Subject: [PATCH 2/7] build: address review feedback on ESM conversion - Bump to 4.0.0: the output format change is breaking, and the `3.2.0-` prerelease scheme means a `^3.2.0-` range would otherwise let `npm update` move consumers onto the ESM build silently. - Use a `default` export condition instead of `import`, so `require()` still resolves on Node 22.12+ via `require(esm)` instead of failing with ERR_PACKAGE_PATH_NOT_EXPORTED. - Restore deep imports via a `./dist/*` subpath export, preserving the specifiers that worked before the `exports` map was introduced. - Move the protoc invocation into a `generate` npm script so CI and the docs cannot drift; CLAUDE.md documented flags that no longer compiled under NodeNext. - Add a CI step that packs the tarball and verifies both the ESM import and the CJS require against the real `exports` map. - Document the module format in README.npm.md and ignore `dist/`. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci-typescript.yaml | 30 ++++++++++++++++++---------- .gitignore | 3 +++ CLAUDE.md | 20 +++++++++---------- README.npm.md | 23 +++++++++++++++++++++ package.json | 8 +++++--- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci-typescript.yaml b/.github/workflows/ci-typescript.yaml index 9d382b1a..c2cae32c 100644 --- a/.github/workflows/ci-typescript.yaml +++ b/.github/workflows/ci-typescript.yaml @@ -37,21 +37,29 @@ jobs: pnpm install - name: Generate - run: | - mkdir -p ./out/ && - protoc protobuf_definitions/*.proto \ - --plugin=./node_modules/.bin/protoc-gen-ts_proto \ - --proto_path=protobuf_definitions \ - --ts_proto_out=./out \ - --ts_proto_opt=outputIndex=true \ - --ts_proto_opt=globalThisPolyfill=true \ - --ts_proto_opt=useExactTypes=false \ - --ts_proto_opt=esModuleInterop=true \ - --ts_proto_opt=importSuffix=.js + run: pnpm run generate - name: Compile run: pnpm run build + - name: Verify package entrypoints + run: | + SMOKE=$(mktemp -d) + TARBALL=$(pnpm pack --pack-destination "$SMOKE" | tail -1) + cd "$SMOKE" + npm init -y > /dev/null + npm install "$TARBALL" > /dev/null + node --input-type=module -e " + import { blueye } from '@blueyerobotics/protocol-definitions'; + if (!blueye.protocol.GetBatteryReq) throw new Error('missing expected export'); + console.log('ESM import OK'); + " + node --input-type=commonjs -e " + const { blueye } = require('@blueyerobotics/protocol-definitions'); + if (!blueye.protocol.GetBatteryReq) throw new Error('missing expected export'); + console.log('CJS require OK'); + " + - name: Publish to npm run: | cp README.npm.md README.md diff --git a/.gitignore b/.gitignore index a4b51f5d..afc52765 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ bin obj out +# typescript outputs +dist + # Other build build_imx diff --git a/CLAUDE.md b/CLAUDE.md index 3f3ae27d..c6e44e63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,17 +22,17 @@ cmake --install build --prefix /usr/local ### TypeScript ``` pnpm install -# Generate TypeScript from proto files: -mkdir -p ./out/ && protoc protobuf_definitions/*.proto \ - --plugin=./node_modules/.bin/protoc-gen-ts_proto \ - --proto_path=protobuf_definitions \ - --ts_proto_out=./out \ - --ts_proto_opt=outputIndex=true \ - --ts_proto_opt=globalThisPolyfill=true \ - --ts_proto_opt=useExactTypes=false -# Compile TypeScript: -pnpm run build +pnpm run generate # generate TypeScript from proto files into ./out (requires protoc) +pnpm run build # compile ./out into ./dist ``` +The protoc flags live in the `generate` script in `package.json`; CI runs the same +script, so keep changes in one place. `--ts_proto_opt=importSuffix=.js` is required — +the package compiles as ESM with `moduleResolution: NodeNext`, which needs explicit +file extensions on relative imports. + +The npm package is ESM-only (`"type": "module"` plus an `exports` map). The `.` entry +uses a `default` condition rather than `import`, so `require()` still resolves on +Node.js 22.12+ via `require(esm)`. ### C#/.NET ``` diff --git a/README.npm.md b/README.npm.md index 95bd9896..68938c10 100644 --- a/README.npm.md +++ b/README.npm.md @@ -8,6 +8,29 @@ TypeScript protobuf definitions for Blueye Robotics protocols generated using [t npm install @blueyerobotics/protocol-definitions ``` +## Module format + +This package is published as **ESM only**. `import` works natively: + +```ts +import { blueye } from "@blueyerobotics/protocol-definitions"; +``` + +CommonJS consumers can still `require()` it on Node.js 22.12+ (or 20.19+), which +supports `require()` of ES modules: + +```js +const { blueye } = require("@blueyerobotics/protocol-definitions"); +``` + +On older Node.js versions `require()` fails with `ERR_REQUIRE_ESM` — use `import` +or a dynamic `await import()` instead. + +The package root is the supported entry point. Individual generated modules stay +reachable under `./dist/` (for example +`@blueyerobotics/protocol-definitions/dist/telemetry.js`) if you want to import a +single protocol file to keep bundles small. + ## Usage ```ts diff --git a/package.json b/package.json index e5dbd325..73988db1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blueyerobotics/protocol-definitions", - "version": "3.2.0", + "version": "4.0.0", "license": "LGPL-3.0-only", "description": "TypeScript definitions for Blueye Robotics protocols", "repository": { @@ -11,14 +11,16 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } + "default": "./dist/index.js" + }, + "./dist/*": "./dist/*" }, "types": "./dist/index.d.ts", "files": [ "dist" ], "scripts": { + "generate": "rm -rf out && mkdir -p out && protoc protobuf_definitions/*.proto --plugin=./node_modules/.bin/protoc-gen-ts_proto --proto_path=protobuf_definitions --ts_proto_out=./out --ts_proto_opt=outputIndex=true --ts_proto_opt=globalThisPolyfill=true --ts_proto_opt=useExactTypes=false --ts_proto_opt=esModuleInterop=true --ts_proto_opt=importSuffix=.js", "build": "tsc" }, "dependencies": { From eae27cda4200287f2be4315903a34aaea6056371 Mon Sep 17 00:00:00 2001 From: Arechi Date: Tue, 18 Aug 2026 21:04:01 +0200 Subject: [PATCH 3/7] docs: clarify module format in npm README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "ESM only" reads as "you cannot require this", which is not true here — require() resolves on Node 22.12+ via require(esm). Lead with what both loaders actually do instead. Co-Authored-By: Claude Opus 5 --- README.npm.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.npm.md b/README.npm.md index 68938c10..df805664 100644 --- a/README.npm.md +++ b/README.npm.md @@ -10,14 +10,15 @@ npm install @blueyerobotics/protocol-definitions ## Module format -This package is published as **ESM only**. `import` works natively: +This package ships ES modules only — there is no separate CommonJS build. Both +`import` and `require()` work on current Node.js releases. ```ts import { blueye } from "@blueyerobotics/protocol-definitions"; ``` -CommonJS consumers can still `require()` it on Node.js 22.12+ (or 20.19+), which -supports `require()` of ES modules: +`require()` is supported on Node.js 22.12+ (or 20.19+), which can load an ES +module from CommonJS: ```js const { blueye } = require("@blueyerobotics/protocol-definitions"); From c56e02382cf85c119e52828104b8a095cb502b96 Mon Sep 17 00:00:00 2001 From: Arechi Date: Tue, 18 Aug 2026 21:04:21 +0200 Subject: [PATCH 4/7] docs: clarify module format in CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match the README wording — "ESM-only" implies require() is unavailable, which it is not. Also record why the exports map uses a `default` condition, so it does not get "corrected" back to `import` later. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c6e44e63..0440ca9d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,9 +30,11 @@ script, so keep changes in one place. `--ts_proto_opt=importSuffix=.js` is requi the package compiles as ESM with `moduleResolution: NodeNext`, which needs explicit file extensions on relative imports. -The npm package is ESM-only (`"type": "module"` plus an `exports` map). The `.` entry -uses a `default` condition rather than `import`, so `require()` still resolves on -Node.js 22.12+ via `require(esm)`. +The npm package ships ES modules only — `"type": "module"` plus an `exports` map, +with no separate CommonJS build. The `.` entry uses a `default` condition rather +than `import`, so `require()` still resolves on Node.js 22.12+ via `require(esm)`; +an `import` condition would match ESM callers only and fail everything else with +`ERR_PACKAGE_PATH_NOT_EXPORTED`. The `./dist/*` subpath keeps deep imports working. ### C#/.NET ``` From 24b386b83ddd83bd8575abeffd6efb365aec9c0e Mon Sep 17 00:00:00 2001 From: Arechi Date: Fri, 21 Aug 2026 12:03:01 +0200 Subject: [PATCH 5/7] docs: state the protocol version in the npm README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published README (README.npm.md, copied over README.md at publish time) never mentioned that this implements protocol v3, so npm consumers had no signal beyond inferring it from the leading version digit. That inference was never reliable — the NuGet package sits at 5.4.0 for the same protocol v3 — so state it explicitly and note that the package version is a separate axis. Co-Authored-By: Claude Opus 5 --- README.npm.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.npm.md b/README.npm.md index df805664..efb261c5 100644 --- a/README.npm.md +++ b/README.npm.md @@ -2,6 +2,16 @@ TypeScript protobuf definitions for Blueye Robotics protocols generated using [ts-proto](https://github.com/stephenh/ts-proto). +## Protocol version + +This package implements **version 3** of the Blueye communication protocol, used by +drones running Blunux 3.0 and newer. Older drones use the separate +[legacy protocol](https://github.com/BluEye-Robotics/blueye.legacyprotocol). + +The npm version above tracks releases of *this package* — packaging, module format +and generated API surface — and is independent of the protocol version. A major +version bump here does not indicate a new protocol generation. + ## Installation ```bash From 0a143a651e1860e49e9b7a3f4d649e0ee94ef1af Mon Sep 17 00:00:00 2001 From: Arechi Date: Fri, 25 Sep 2026 15:19:49 +0200 Subject: [PATCH 6/7] build: keep extensionless deep imports and package.json exported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `exports` targets never get an extension appended, so `"./dist/*": "./dist/*"` broke specifiers like `…/dist/mission_planning` that resolved before the exports map existed — BlueyeCloud imports this way in ~18 files. Map `./dist/*` to `./dist/*.js`, with a `./dist/*.js` entry taking precedence for specifiers that already carry the extension. Also export `./package.json`, which the exports map otherwise blocks. The CI smoke test now covers both deep-import forms and the package.json subpath. Drop the protocol-version section from the npm README; it existed to explain a 4.0.0 bump that is no longer happening. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci-typescript.yaml | 8 ++++++++ CLAUDE.md | 5 ++++- README.npm.md | 14 ++------------ package.json | 4 +++- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-typescript.yaml b/.github/workflows/ci-typescript.yaml index 5bab1446..d73ab493 100644 --- a/.github/workflows/ci-typescript.yaml +++ b/.github/workflows/ci-typescript.yaml @@ -59,6 +59,14 @@ jobs: if (!blueye.protocol.GetBatteryReq) throw new Error('missing expected export'); console.log('CJS require OK'); " + node --input-type=module -e " + for (const spec of ['dist/mission_planning', 'dist/mission_planning.js']) { + const { Mission } = await import('@blueyerobotics/protocol-definitions/' + spec); + if (!Mission) throw new Error('missing expected export from ' + spec); + } + await import('@blueyerobotics/protocol-definitions/package.json', { with: { type: 'json' } }); + console.log('Deep imports OK'); + " - name: Publish to npm run: | diff --git a/CLAUDE.md b/CLAUDE.md index 2e5e08ec..b220d314 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,10 @@ The npm package ships ES modules only — `"type": "module"` plus an `exports` m with no separate CommonJS build. The `.` entry uses a `default` condition rather than `import`, so `require()` still resolves on Node.js 22.12+ via `require(esm)`; an `import` condition would match ESM callers only and fail everything else with -`ERR_PACKAGE_PATH_NOT_EXPORTED`. The `./dist/*` subpath keeps deep imports working. +`ERR_PACKAGE_PATH_NOT_EXPORTED`. Deep imports resolve through two `./dist/*` entries: +`exports` targets never get an extension appended, so `./dist/*` maps to `./dist/*.js` +to keep extensionless specifiers like `…/dist/mission_planning` working, and +`./dist/*.js` takes precedence for specifiers that already end in `.js`. ### C#/.NET ``` diff --git a/README.npm.md b/README.npm.md index efb261c5..cbfdc491 100644 --- a/README.npm.md +++ b/README.npm.md @@ -2,16 +2,6 @@ TypeScript protobuf definitions for Blueye Robotics protocols generated using [ts-proto](https://github.com/stephenh/ts-proto). -## Protocol version - -This package implements **version 3** of the Blueye communication protocol, used by -drones running Blunux 3.0 and newer. Older drones use the separate -[legacy protocol](https://github.com/BluEye-Robotics/blueye.legacyprotocol). - -The npm version above tracks releases of *this package* — packaging, module format -and generated API surface — and is independent of the protocol version. A major -version bump here does not indicate a new protocol generation. - ## Installation ```bash @@ -38,8 +28,8 @@ On older Node.js versions `require()` fails with `ERR_REQUIRE_ESM` — use `impo or a dynamic `await import()` instead. The package root is the supported entry point. Individual generated modules stay -reachable under `./dist/` (for example -`@blueyerobotics/protocol-definitions/dist/telemetry.js`) if you want to import a +reachable under `./dist/`, with or without the `.js` extension (for example +`@blueyerobotics/protocol-definitions/dist/telemetry`) if you want to import a single protocol file to keep bundles small. ## Usage diff --git a/package.json b/package.json index 2a9a589c..070f8f71 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, - "./dist/*": "./dist/*" + "./dist/*.js": "./dist/*.js", + "./dist/*": "./dist/*.js", + "./package.json": "./package.json" }, "types": "./dist/index.d.ts", "files": [ From 3449ecd65619637f2dab4aca0556194dd393fedd Mon Sep 17 00:00:00 2001 From: Arechi Date: Fri, 25 Sep 2026 15:22:59 +0200 Subject: [PATCH 7/7] ci: build and smoke-test the TypeScript package on pull requests The workflow only ran on pushes to master, so generation, compilation and the entrypoint smoke test were first exercised after merge. Run them on pull requests too, and limit the npm publish and the blueye-ts bump dispatch to pushes. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci-typescript.yaml | 3 +++ CLAUDE.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-typescript.yaml b/.github/workflows/ci-typescript.yaml index d73ab493..7aadefe9 100644 --- a/.github/workflows/ci-typescript.yaml +++ b/.github/workflows/ci-typescript.yaml @@ -4,6 +4,7 @@ on: push: branches: - master + pull_request: permissions: id-token: write # Required for OIDC @@ -69,6 +70,7 @@ jobs: " - name: Publish to npm + if: github.event_name == 'push' run: | cp README.npm.md README.md VERSION=$(pnpm pkg get version | tr -d '"') @@ -78,6 +80,7 @@ jobs: echo "PROTOCOL_VERSION=${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV" - name: Trigger protocol bump in blueye-ts + if: github.event_name == 'push' env: GH_TOKEN: ${{ secrets.BLUEYE_PROTOCOL_BUMP_PAT }} run: | diff --git a/CLAUDE.md b/CLAUDE.md index b220d314..ca10cfa4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,7 @@ All proto definitions are in `protobuf_definitions/`: ## CI/CD Pipeline - **ci-build.yaml** — Protolint validation (all pushes and PRs) -- **ci-typescript.yaml** — Generate TS, compile, publish to npm (master only) +- **ci-typescript.yaml** — Generate TS, compile, smoke-test the packed tarball (PRs and master); publish to npm and trigger the blueye-ts bump (master only) - **ci-dotnet.yaml** — Build and publish NuGet package (all pushes) - **ci-python.yaml** — Triggers `blueye.protocol` repo update via repository dispatch (master, when proto files change) - **gen-docs.yaml** — Generate HTML docs, upload to Azure (master only)