From 4fb154e446077c6db3e06b376936a40c018bdc85 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Wed, 5 Aug 2026 12:36:54 +0200 Subject: [PATCH] feat: publish to NPM instead of JSR The package is published to NPM now, instead of JSR. The TypeScript sources are compiled to JavaScript using `@swc/core` and the type declarations are emitted using TypeScript 7. Both steps run from the `prepublishOnly` hook into the `npm/` folder, which is what `exports` points to. This mirrors the setup used in `nRFCloud/billing-service-proto`. `semantic-release` publishes via `@semantic-release/npm` instead of `@sebbo2002/semantic-release-jsr`. --- .github/workflows/test-and-release.yaml | 5 +- .gitignore | 1 + .npm/compile.ts | 50 +++++ .npm/tsconfig.npm.json | 11 + .npm/updateImports.spec.ts | 11 + .npm/updateImports.ts | 2 + CONTRIBUTING.md | 17 +- README.md | 4 +- jsr.json | 7 - package-lock.json | 281 ++++++++++++++++++++++++ package.json | 29 ++- tsconfig.json | 4 +- 12 files changed, 404 insertions(+), 18 deletions(-) create mode 100644 .npm/compile.ts create mode 100644 .npm/tsconfig.npm.json create mode 100644 .npm/updateImports.spec.ts create mode 100644 .npm/updateImports.ts delete mode 100644 jsr.json diff --git a/.github/workflows/test-and-release.yaml b/.github/workflows/test-and-release.yaml index 9330ed3..68b5d48 100644 --- a/.github/workflows/test-and-release.yaml +++ b/.github/workflows/test-and-release.yaml @@ -21,6 +21,7 @@ jobs: with: node-version: ">=24.18.1 <25" cache: "npm" + registry-url: "https://registry.npmjs.org" - name: Install NPM version specified in package.json uses: ./.github/actions/install-npm @@ -33,6 +34,4 @@ jobs: - run: npm test - name: Semantic release - run: | - npm install -D @sebbo2002/semantic-release-jsr - npx semantic-release + run: npx semantic-release diff --git a/.gitignore b/.gitignore index c2658d7..4f5a8ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +npm/ diff --git a/.npm/compile.ts b/.npm/compile.ts new file mode 100644 index 0000000..a620af7 --- /dev/null +++ b/.npm/compile.ts @@ -0,0 +1,50 @@ +/* + * Compile source for NPM + */ + +import swc from '@swc/core' +import { mkdirSync, rmSync, writeFileSync } from 'node:fs' +import { glob } from 'node:fs/promises' +import path, { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import tsconfig from './tsconfig.npm.json' with { type: 'json' } +import { updateImports } from './updateImports.ts' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const outDir = path.join(__dirname, '..', 'npm') + +try { + rmSync(outDir, { recursive: true, force: true }) +} catch { + // pass +} + +for await (const file of glob( + tsconfig.include.map((include) => path.join('.npm', include)), +)) { + if (file.endsWith('.spec.ts') || file.endsWith('.test.ts')) continue + let compiled = ( + await swc.transformFile(file, { + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es2024', + }, + module: { + type: 'es6', + }, + }) + ).code + + compiled = updateImports(compiled) + + const targetFile = path.join(outDir, file.replace(/\.ts$/, '.js')) + + mkdirSync(dirname(targetFile), { recursive: true }) + + writeFileSync(targetFile, compiled, 'utf8') + + console.log(file) +} diff --git a/.npm/tsconfig.npm.json b/.npm/tsconfig.npm.json new file mode 100644 index 0000000..200a429 --- /dev/null +++ b/.npm/tsconfig.npm.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "emitDeclarationOnly": true, + "declaration": true, + "noEmit": false, + "rootDir": ".." + }, + "include": ["../*.ts"], + "exclude": ["../*.spec.ts", "../*.test.ts"] +} diff --git a/.npm/updateImports.spec.ts b/.npm/updateImports.spec.ts new file mode 100644 index 0000000..63f7480 --- /dev/null +++ b/.npm/updateImports.spec.ts @@ -0,0 +1,11 @@ +import assert from 'node:assert' +import { describe, it } from 'node:test' +import { updateImports } from './updateImports.ts' + +void describe('updateImports', () => { + void it('replaces .ts with .js in relative imports', () => { + const input = `import { foo } from './bar.ts';` + const expected = `import { foo } from './bar.js';` + assert.equal(updateImports(input), expected) + }) +}) diff --git a/.npm/updateImports.ts b/.npm/updateImports.ts new file mode 100644 index 0000000..b291fbe --- /dev/null +++ b/.npm/updateImports.ts @@ -0,0 +1,2 @@ +export const updateImports = (source: string): string => + source.replace(/from ['"](.+?)\.ts['"]/g, "from '$1.js'") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index acbf277..b642122 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ `@nrfcloud/lambda-helpers` is a published library: it provides helper functions for AWS Lambda functions used in nRF Cloud and is published to -[JSR](https://jsr.io/@nrfcloud/lambda-helpers). +[NPM](https://www.npmjs.com/package/@nrfcloud/lambda-helpers). ## Development setup @@ -18,6 +18,19 @@ for AWS Lambda functions used in nRF Cloud and is published to 1. Run `npx tsc` to type-check the project. 1. Run `npm test` to run the unit tests. +## Building the NPM package + +The package is published as compiled JavaScript with type declarations in the +`npm/` folder, which is created by the `prepublishOnly` hook: + +1. [`.npm/compile.ts`](.npm/compile.ts) transpiles the TypeScript sources using + [`@swc/core`](https://www.npmjs.com/package/@swc/core) and rewrites the `.ts` + import specifiers to `.js`. +1. [TypeScript 7](https://www.npmjs.com/package/typescript) emits the type + declarations, using [`.npm/tsconfig.npm.json`](.npm/tsconfig.npm.json). + +Run `npm run prepublishOnly` to build it locally. + ## Releasing a new version 1. Finally, create a commit that packages up all the changes. @@ -29,7 +42,7 @@ for AWS Lambda functions used in nRF Cloud and is published to 1. Once approved and CI passes, rebase or squash away! 1. [`semantic-release` in the Test&Release workflow](.github/workflows/test-and-release.yaml) takes care of creating a new GitHub release and publishing the package to - [JSR](https://jsr.io/@nrfcloud/lambda-helpers). + [NPM](https://www.npmjs.com/package/@nrfcloud/lambda-helpers). Now that you have a new version of `@nrfcloud/lambda-helpers` published, you can bump the dependency in the projects that consume it. diff --git a/README.md b/README.md index aed3c3d..892c3a3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # `@nrfcloud/lambda-helpers` - + Helper functions for AWS Lambda functions used in nRF Cloud. ## Install with NPM ```bash -npx jsr add (--save-prod|--save-dev) @nrfcloud/lambda-helpers +npm i (--save-prod|--save-dev) @nrfcloud/lambda-helpers ``` ## Usage diff --git a/jsr.json b/jsr.json deleted file mode 100644 index 830c7c8..0000000 --- a/jsr.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://jsr.io/schema/config-file.v1.json", - "name": "@nrfcloud/lambda-helpers", - "version": "0.0.0-development", - "license": "BSD-3-Clause", - "exports": "./export.ts" -} diff --git a/package-lock.json b/package-lock.json index 51ebb84..b2ec119 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@bifravst/prettier-config": "1.1.17", "@nrfcloud/validate-with-typebox": "npm:@jsr/nrfcloud__validate-with-typebox@^1.0.5", + "@swc/core": "1.15.47", "@types/aws-lambda": "8.10.162", "@types/node": "25.9.5", "@typescript/native": "npm:typescript@7.0.2", @@ -180,6 +181,286 @@ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "license": "MIT" }, + "node_modules/@swc/core": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.47.tgz", + "integrity": "sha512-FbsO5JcfOjfH38W/rohBRBweJeERsAuIP4f377lmkmxTcq9exjtx4SkRuZY5CdfhR2CBVwDIJegBpJDffwNsOg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.47", + "@swc/core-darwin-x64": "1.15.47", + "@swc/core-linux-arm-gnueabihf": "1.15.47", + "@swc/core-linux-arm64-gnu": "1.15.47", + "@swc/core-linux-arm64-musl": "1.15.47", + "@swc/core-linux-ppc64-gnu": "1.15.47", + "@swc/core-linux-s390x-gnu": "1.15.47", + "@swc/core-linux-x64-gnu": "1.15.47", + "@swc/core-linux-x64-musl": "1.15.47", + "@swc/core-win32-arm64-msvc": "1.15.47", + "@swc/core-win32-ia32-msvc": "1.15.47", + "@swc/core-win32-x64-msvc": "1.15.47" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.47.tgz", + "integrity": "sha512-GsoMtan3ojGGMGFbl31mmRu5ctZ56re8grGE8mO/OHJ8O+JRkzod02fe7X6ZQ8JvamA3imkEkx/h3u+vsOgPgA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.47.tgz", + "integrity": "sha512-leTi7Rx3KF4zcC637iqWgk9SoV8VXAD8ppQYXsep63px5A/UftOcxLN1pmr8Z1si/YvX90ompP/rHgpYkgwXWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.47.tgz", + "integrity": "sha512-hBqHuoWKKIsKmDBn9qVeWqj5GWZhtlcczVaqQmNRXsDfq+voR5CxKRfamA367QjJXtceYuliLFfEL8QsskRM2g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.47.tgz", + "integrity": "sha512-TBxvRz+B4K205TWHHZxWVxkC2RFNP/Mz3PNcECBos5PsKwxjg3QSJzdoebr0VCf0Bfh8HOPldKxAP/8XkFe9gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.47.tgz", + "integrity": "sha512-3Yu3Uq/VgytqsPjTMbkPU1ExADytbdWbruJYhA584E9jrpE2Ki+R6VVPoZCeAVk1Cb7QxcRTgblw6bSa6a/R+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.47.tgz", + "integrity": "sha512-wfdMi5IaOaNtmh2/6geRoxIdNfqylUZFdtzTKS655y1axWfIWyx7As74vv0wVdjeCIZ3WmCI9odDd4rUttXOSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.47.tgz", + "integrity": "sha512-3hHYBY0yx8Ez7GMRrkhXHQzMdR5IZA6Wq5Ee4svlgwvSECLpnAJ9+0AimEGUFDvuLwE7nV/2+PYe8+Nm4rvNcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.47.tgz", + "integrity": "sha512-TjfhjgP/jGCfFHYC3JQPhJA1HwErbIJ9JfREDc1KNkvY6P0LodCgKVIlQ5deeTbkG7ih3bF5PHJLuLpaZjdRyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.47.tgz", + "integrity": "sha512-CQpS8Ge/avfjZd0UEwG/sds83Uu32deQXcV1Jo3jD0mmvQQqtYAjpsDZXugmheeAwmt+YIuoVtVHro8LMYHqsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.47.tgz", + "integrity": "sha512-0W8IKHsUTYiT7G2RqtOoVWk+89yzZikIiDUb/sCK6BmQDBhN91hQSfyUtW12jhEWLzYgcfmisfsZrmZE+84U1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.47.tgz", + "integrity": "sha512-ZIp49d2Z4/ka2jO9otOg4hDvTdPmp86kVOgS2M5FCPI7eKKZ1W0boxWn+8XeZrfERtFGW0AlMRm4JhlJa7l3NA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.47", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.47.tgz", + "integrity": "sha512-2h8Iek95vnixkBRCo+H8p09+Q5ll2NgSMFrWTy0iKt7+/t+8/T5mBpiT6c0ZxSS7wcWjwZ9sGZkK70tTSYHdDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@types/aws-lambda": { "version": "8.10.162", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", diff --git a/package.json b/package.json index 9528f4b..435e41a 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,30 @@ }, "author": "Nordic Semiconductor ASA | nordicsemi.no", "scripts": { - "test": "node --no-warnings --experimental-transform-types --test \"./*.spec.ts\"" + "test": "node --no-warnings --experimental-transform-types --test \"./*.spec.ts\" \"./.npm/*.spec.ts\"", + "prepublishOnly": "node --experimental-strip-types ./.npm/compile.ts && npx tsc -P ./.npm/tsconfig.npm.json --outDir ./npm" }, "license": "BSD-3-Clause", "type": "module", - "main": "export.ts", + "exports": { + ".": { + "import": { + "types": "./npm/export.d.ts", + "default": "./npm/export.js" + } + } + }, + "files": [ + "npm", + "README.md" + ], + "publishConfig": { + "access": "public" + }, "devDependencies": { "@bifravst/prettier-config": "1.1.17", "@nrfcloud/validate-with-typebox": "npm:@jsr/nrfcloud__validate-with-typebox@^1.0.5", + "@swc/core": "1.15.47", "@types/aws-lambda": "8.10.162", "@types/node": "25.9.5", "@typescript/native": "npm:typescript@7.0.2", @@ -35,7 +51,14 @@ "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", - "@sebbo2002/semantic-release-jsr" + "@semantic-release/npm", + [ + "@semantic-release/github", + { + "successCommentCondition": false, + "failCommentCondition": false + } + ] ] }, "engines": { diff --git a/tsconfig.json b/tsconfig.json index c37062f..84b4b81 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,7 @@ "noUnusedLocals": true, "allowImportingTsExtensions": true, "types": ["node"] - } + }, + "exclude": ["npm/**/*"], + "include": ["./**/*.ts", "./.npm/*.ts"] }