diff --git a/.github/workflows/ci-typescript.yaml b/.github/workflows/ci-typescript.yaml index f60672ff..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 @@ -37,20 +38,39 @@ 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 + 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'); + " + 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 + if: github.event_name == 'push' run: | cp README.npm.md README.md VERSION=$(pnpm pkg get version | tr -d '"') @@ -60,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/.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 50b698a4..ca10cfa4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,17 +22,22 @@ 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 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`. 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 ``` @@ -71,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) diff --git a/README.npm.md b/README.npm.md index 95bd9896..cbfdc491 100644 --- a/README.npm.md +++ b/README.npm.md @@ -8,6 +8,30 @@ TypeScript protobuf definitions for Blueye Robotics protocols generated using [t npm install @blueyerobotics/protocol-definitions ``` +## Module format + +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"; +``` + +`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"); +``` + +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/`, 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 ```ts diff --git a/package.json b/package.json index 00d313b8..070f8f71 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,28 @@ { "name": "@blueyerobotics/protocol-definitions", - "version": "3.3.0", + "version": "3.4.0", "license": "LGPL-3.0-only", "description": "TypeScript definitions for Blueye Robotics protocols", "repository": { "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", + "default": "./dist/index.js" + }, + "./dist/*.js": "./dist/*.js", + "./dist/*": "./dist/*.js", + "./package.json": "./package.json" + }, + "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": { 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,