├── .githooks └── pre-commit ├── test ├── tsconfig.json └── eventmit.test.ts ├── example.ts ├── release-notes.md ├── .github ├── workflows │ ├── test.yml │ ├── create-release-pr.yml │ └── release.yml └── release.yml ├── src └── eventmit.ts ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── .gitignore /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npx --no-install lint-staged 3 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "allowImportingTsExtensions": true 6 | }, 7 | "include": [ 8 | "../src/**/*", 9 | "**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /example.ts: -------------------------------------------------------------------------------- 1 | import { eventmit } from "./src/eventmit.js"; 2 | 3 | const event = eventmit<{ key: string }>(); 4 | event.on((value) => { 5 | console.log(1, value); 6 | }); 7 | event.on((value) => { 8 | console.log(2, value); 9 | }); 10 | event.emit({ 11 | key: "value" 12 | }); 13 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What's Changed 4 | ### CI 5 | * feat: dynamically fetch package name from package.json in release workflow by @azu in https://github.com/azu/eventmit/pull/14 6 | 7 | 8 | **Full Changelog**: https://github.com/azu/eventmit/compare/v3.0.4...v3.0.5 9 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: "Test on Node.js ${{ matrix.node_version }}" 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | node_version: [ 22 ] 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 13 | - name: setup Node.js ${{ matrix.node_version }} 14 | uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 15 | with: 16 | node-version: ${{ matrix.node_version }} 17 | - name: Install 18 | run: npm ci 19 | - name: Test 20 | run: npm test 21 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - 'Type: Meta' 5 | - 'Type: Question' 6 | - 'Type: Release' 7 | 8 | categories: 9 | - title: Security Fixes 10 | labels: ['Type: Security'] 11 | - title: Breaking Changes 12 | labels: ['Type: Breaking Change'] 13 | - title: Features 14 | labels: ['Type: Feature'] 15 | - title: Bug Fixes 16 | labels: ['Type: Bug'] 17 | - title: Documentation 18 | labels: ['Type: Documentation'] 19 | - title: Refactoring 20 | labels: ['Type: Refactoring'] 21 | - title: Testing 22 | labels: ['Type: Testing'] 23 | - title: Maintenance 24 | labels: ['Type: Maintenance'] 25 | - title: CI 26 | labels: ['Type: CI'] 27 | - title: Dependency Updates 28 | labels: ['Type: Dependencies', "dependencies"] 29 | - title: Other Changes 30 | labels: ['*'] 31 | -------------------------------------------------------------------------------- /src/eventmit.ts: -------------------------------------------------------------------------------- 1 | export type EventmitHandler = (value: T) => any; 2 | 3 | export type Eventmitter = { 4 | /** 5 | * Register an event handler 6 | */ 7 | on: (handler: EventmitHandler) => void; 8 | /** 9 | * Remove an event handler 10 | */ 11 | off: (handler: EventmitHandler) => void; 12 | /** 13 | * Remove all event handlers 14 | */ 15 | offAll: () => void; 16 | /** 17 | * Invoke all handlers 18 | */ 19 | emit: (value: T) => void; 20 | }; 21 | 22 | export const eventmit: () => Eventmitter = () => { 23 | const set = new Set>(); 24 | return { 25 | on(handler: EventmitHandler) { 26 | set.add(handler); 27 | }, 28 | off(handler: EventmitHandler) { 29 | set.delete(handler); 30 | }, 31 | offAll() { 32 | set.clear(); 33 | }, 34 | emit(value: T) { 35 | set.forEach((handler) => handler(value)); 36 | }, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "newLine": "LF", 8 | "outDir": "./module/", 9 | "target": "ES2024", 10 | "sourceMap": true, 11 | "declaration": true, 12 | "verbatimModuleSyntax": true, 13 | "jsx": "preserve", 14 | "lib": [ 15 | "esnext", 16 | "dom" 17 | ], 18 | /* Strict Type-Checking Options */ 19 | "strict": true, 20 | /* Additional Checks */ 21 | /* Report errors on unused locals. */ 22 | "noUnusedLocals": true, 23 | /* Report errors on unused parameters. */ 24 | "noUnusedParameters": true, 25 | /* Report error when not all code paths in function return a value. */ 26 | "noImplicitReturns": true, 27 | /* Report errors for fallthrough cases in switch statement. */ 28 | "noFallthroughCasesInSwitch": true 29 | }, 30 | "include": [ 31 | "src/**/*" 32 | ], 33 | "exclude": [ 34 | ".git", 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventmit", 3 | "version": "3.0.6", 4 | "description": "One event per one event.", 5 | "keywords": [ 6 | "events", 7 | "eventemitter" 8 | ], 9 | "homepage": "https://github.com/azu/eventmit", 10 | "bugs": { 11 | "url": "https://github.com/azu/eventmit/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/azu/eventmit.git" 16 | }, 17 | "license": "MIT", 18 | "author": "azu", 19 | "files": [ 20 | "bin/", 21 | "module/", 22 | "src/" 23 | ], 24 | "type": "module", 25 | "source": "./src/eventmit.ts", 26 | "main": "./module/eventmit.js", 27 | "types": "./module/eventmit.d.ts", 28 | "exports": "./module/eventmit.js", 29 | "directories": { 30 | "lib": "lib", 31 | "test": "test" 32 | }, 33 | "scripts": { 34 | "build": "tsc -p .", 35 | "clean": "git clean -fx module/", 36 | "prepublish": "npm run --if-present build", 37 | "test": "node --run build && node --run test:unit", 38 | "test:build": "tsc -p test", 39 | "test:unit": "node --experimental-strip-types --test \"test/**/*.ts\"", 40 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"", 41 | "prepare": "git config --local core.hooksPath .githooks" 42 | }, 43 | "lint-staged": { 44 | "*.{js,jsx,ts,tsx,css}": [ 45 | "prettier --write" 46 | ] 47 | }, 48 | "prettier": { 49 | "singleQuote": false, 50 | "printWidth": 120, 51 | "tabWidth": 4, 52 | "trailingComma": "none" 53 | }, 54 | "devDependencies": { 55 | "@types/node": "^22.15.21", 56 | "lint-staged": "^16.0.0", 57 | "prettier": "^3.5.3", 58 | "typescript": "^5.8.3" 59 | }, 60 | "packageManager": "npm@11.0.0+sha512.11dff29565d2297c74e7c594a9762581bde969f0aa5cbe6f5b3644bf008a16c065ece61094d9ffbb81125be38df8e1ba43eb8244b3d30c61eb797e9a2440e3ec" 61 | } 62 | -------------------------------------------------------------------------------- /test/eventmit.test.ts: -------------------------------------------------------------------------------- 1 | import type { EventmitHandler } from "../src/eventmit.ts"; 2 | import { eventmit } from "../src/eventmit.ts"; 3 | import { describe, it } from "node:test"; 4 | import assert from "assert"; 5 | 6 | describe("eventmit", function () { 7 | it("emit payload and handler receive the payload", () => { 8 | const caller: [number, string][] = []; 9 | const event = eventmit(); 10 | event.on((value) => caller.push([1, value])); 11 | event.on((value) => caller.push([2, value])); 12 | const payload = "payload value"; 13 | event.emit(payload); 14 | assert.deepStrictEqual(caller, [ 15 | [1, payload], 16 | [2, payload] 17 | ]); 18 | }); 19 | it("unregister handler", () => { 20 | const caller: [number, string][] = []; 21 | const event = eventmit(); 22 | const handler: EventmitHandler = (value) => caller.push([1, value]); 23 | event.on(handler); 24 | event.emit("payload 1"); 25 | assert.deepStrictEqual(caller, [[1, "payload 1"]]); 26 | event.off(handler); 27 | event.emit("payload 2"); 28 | assert.deepStrictEqual(caller, [[1, "payload 1"]]); 29 | }); 30 | it("unregister all handler", () => { 31 | const caller: [number, string][] = []; 32 | const event = eventmit(); 33 | const handler1: EventmitHandler = (value) => caller.push([1, value]); 34 | const handler2: EventmitHandler = (value) => caller.push([2, value]); 35 | event.on(handler1); 36 | event.on(handler2); 37 | event.emit("payload"); 38 | assert.deepStrictEqual(caller, [ 39 | [1, "payload"], 40 | [2, "payload"] 41 | ]); 42 | event.offAll(); 43 | event.emit("payload 2"); 44 | assert.deepStrictEqual(caller, [ 45 | [1, "payload"], 46 | [2, "payload"] 47 | ]); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eventmit [![Actions Status](https://github.com/azu/eventmit/workflows/test/badge.svg)](https://github.com/azu/eventmit/actions?query=workflow%3A"test") 2 | 3 | A single event object per an event. 4 | 5 | ## Feature 6 | 7 | - A single event object per an event 8 | - Tiny code base - less 1kb 9 | - Written by TypeScript 10 | 11 | ## Install 12 | 13 | Install with [npm](https://www.npmjs.com/): 14 | 15 | npm install eventmit 16 | 17 | Requirement: ECMAScript 2015 because this library use [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set). 18 | 19 | ## Usage 20 | 21 | Create an eventmit object and register handler and invoke handlers. 22 | 23 | ```ts 24 | import { eventmit } from "eventmit"; 25 | const event = eventmit<{ key: string }>(); 26 | // Register handler 27 | event.on((value) => { 28 | console.log(1, value); 29 | }); 30 | event.on((value) => { 31 | console.log(2, value); 32 | }); 33 | // Invoke handler 34 | event.emit({ 35 | key: "value" 36 | }); 37 | // Unregister handler 38 | event.offAll(); 39 | ``` 40 | 41 | ## API 42 | 43 | ```ts 44 | export declare type EventmitHandler = (value: T) => any; 45 | export declare type Eventmitter = { 46 | /** 47 | * Register an event handler 48 | */ 49 | on: (handler: EventmitHandler) => void; 50 | /** 51 | * Remove an event handler 52 | */ 53 | off: (handler: EventmitHandler) => void; 54 | /** 55 | * Remove all event handlers 56 | */ 57 | offAll: () => void; 58 | /** 59 | * Invoke all handlers 60 | */ 61 | emit: (value: T) => void; 62 | }; 63 | export declare const eventmit: () => Eventmitter; 64 | ``` 65 | 66 | ## ECMAScript Modules 67 | 68 | You can import `eventmit` as ES Modules. 69 | 70 | ```js 71 | import { eventmit } from "https://unpkg.com/eventmit?module"; 72 | const event = eventmit(); 73 | // Register handler 74 | event.on((value) => { 75 | console.log(value); 76 | }); 77 | // Invoke handler 78 | event.emit("value"); 79 | ``` 80 | 81 | It means that eventmit work on Browser and [Deno](https://deno.land/). 82 | 83 | ### Import at Deno 84 | If you are using Deno, import `eventmit` from a URL. 85 | For example, using a CDN: 86 | 87 | ```typescript 88 | import { eventmit } from "https://cdn.skypack.dev/eventmit?dts"; 89 | ``` 90 | 91 | ## Changelog 92 | 93 | See [Releases page](https://github.com/azu/eventmit/releases). 94 | 95 | ## Running tests 96 | 97 | Install devDependencies and Run `npm test`: 98 | 99 | npm test 100 | 101 | ## Contributing 102 | 103 | Pull requests and stars are always welcome. 104 | 105 | For bugs and feature requests, [please create an issue](https://github.com/azu/eventmit/issues). 106 | 107 | 1. Fork it! 108 | 2. Create your feature branch: `git checkout -b my-new-feature` 109 | 3. Commit your changes: `git commit -am 'Add some feature'` 110 | 4. Push to the branch: `git push origin my-new-feature` 111 | 5. Submit a pull request :D 112 | 113 | ## Author 114 | 115 | - [github/azu](https://github.com/azu) 116 | - [twitter/azu_re](https://twitter.com/azu_re) 117 | 118 | ## License 119 | 120 | MIT © azu 121 | 122 | ## Related 123 | 124 | - [developit/mitt: 🥊 Tiny 200 byte functional event emitter / pubsub.](https://github.com/developit/mitt) 125 | - Support multiple event type 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | dist/ 3 | module/ 4 | ### https://raw.github.com/github/gitignore/d2c1bb2b9c72ead618c9f6a48280ebc7a8e0dff6/Node.gitignore 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | .env.test 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | 88 | ### https://raw.github.com/github/gitignore/d2c1bb2b9c72ead618c9f6a48280ebc7a8e0dff6/Global/JetBrains.gitignore 89 | 90 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 91 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 92 | 93 | # User-specific stuff 94 | .idea/**/workspace.xml 95 | .idea/**/tasks.xml 96 | .idea/**/usage.statistics.xml 97 | .idea/**/dictionaries 98 | .idea/**/shelf 99 | 100 | # Generated files 101 | .idea/**/contentModel.xml 102 | 103 | # Sensitive or high-churn files 104 | .idea/**/dataSources/ 105 | .idea/**/dataSources.ids 106 | .idea/**/dataSources.local.xml 107 | .idea/**/sqlDataSources.xml 108 | .idea/**/dynamic.xml 109 | .idea/**/uiDesigner.xml 110 | .idea/**/dbnavigator.xml 111 | 112 | # Gradle 113 | .idea/**/gradle.xml 114 | .idea/**/libraries 115 | 116 | # Gradle and Maven with auto-import 117 | # When using Gradle or Maven with auto-import, you should exclude module files, 118 | # since they will be recreated, and may cause churn. Uncomment if using 119 | # auto-import. 120 | # .idea/modules.xml 121 | # .idea/*.iml 122 | # .idea/modules 123 | 124 | # CMake 125 | cmake-build-*/ 126 | 127 | # Mongo Explorer plugin 128 | .idea/**/mongoSettings.xml 129 | 130 | # File-based project format 131 | *.iws 132 | 133 | # IntelliJ 134 | out/ 135 | 136 | # mpeltonen/sbt-idea plugin 137 | .idea_modules/ 138 | 139 | # JIRA plugin 140 | atlassian-ide-plugin.xml 141 | 142 | # Cursive Clojure plugin 143 | .idea/replstate.xml 144 | 145 | # Crashlytics plugin (for Android Studio and IntelliJ) 146 | com_crashlytics_export_strings.xml 147 | crashlytics.properties 148 | crashlytics-build.properties 149 | fabric.properties 150 | 151 | # Editor-based Rest Client 152 | .idea/httpRequests 153 | 154 | # Android studio 3.1+ serialized cache file 155 | .idea/caches/build_file_checksums.ser 156 | 157 | 158 | /lib 159 | -------------------------------------------------------------------------------- /.github/workflows/create-release-pr.yml: -------------------------------------------------------------------------------- 1 | name: Create Release PR 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version type' 8 | required: true 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | 15 | jobs: 16 | create-release-pr: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | pull-requests: write 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 24 | with: 25 | persist-credentials: false 26 | 27 | - name: Configure Git 28 | run: | 29 | git config user.name "github-actions[bot]" 30 | git config user.email "github-actions[bot]@users.noreply.github.com" 31 | 32 | - name: Setup Node.js 33 | uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 34 | with: 35 | node-version: 'lts/*' 36 | 37 | # No need to install dependencies - npm version works without them 38 | - name: Version bump 39 | id: version 40 | run: | 41 | npm version "$VERSION_TYPE" --no-git-tag-version 42 | VERSION=$(jq -r '.version' package.json) 43 | echo "version=$VERSION" >> $GITHUB_OUTPUT 44 | env: 45 | VERSION_TYPE: ${{ github.event.inputs.version }} 46 | 47 | - name: Get release notes 48 | id: release-notes 49 | run: | 50 | # Get the default branch 51 | DEFAULT_BRANCH=$(gh api "repos/$GITHUB_REPOSITORY" --jq '.default_branch') 52 | 53 | # Get the last release tag using GitHub API 54 | LAST_TAG=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq '.tag_name' 2>/dev/null || echo "") 55 | 56 | # Log the tag status 57 | if [ -z "$LAST_TAG" ]; then 58 | echo "No previous releases found, generating notes from beginning" 59 | else 60 | echo "Previous release: $LAST_TAG" 61 | fi 62 | 63 | # Generate release notes with or without previous tag 64 | if [ -n "$LAST_TAG" ]; then 65 | NOTES=$(gh api \ 66 | --method POST \ 67 | -H "Accept: application/vnd.github+json" \ 68 | "/repos/$GITHUB_REPOSITORY/releases/generate-notes" \ 69 | -f "tag_name=v$VERSION" \ 70 | -f "target_commitish=$DEFAULT_BRANCH" \ 71 | -f "previous_tag_name=$LAST_TAG" \ 72 | --jq '.body') 73 | else 74 | NOTES=$(gh api \ 75 | --method POST \ 76 | -H "Accept: application/vnd.github+json" \ 77 | "/repos/$GITHUB_REPOSITORY/releases/generate-notes" \ 78 | -f "tag_name=v$VERSION" \ 79 | -f "target_commitish=$DEFAULT_BRANCH" \ 80 | --jq '.body') 81 | fi 82 | 83 | # Save to file to handle multiline content 84 | echo "$NOTES" > release-notes.md 85 | env: 86 | GH_TOKEN: ${{ github.token }} 87 | VERSION: ${{ steps.version.outputs.version }} 88 | GITHUB_REPOSITORY: ${{ github.repository }} 89 | 90 | - name: Create Pull Request 91 | uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 92 | with: 93 | branch: release/v${{ steps.version.outputs.version }} 94 | delete-branch: true 95 | title: "Release v${{ steps.version.outputs.version }}" 96 | body-path: release-notes.md 97 | commit-message: "chore: release v${{ steps.version.outputs.version }}" 98 | add-paths: | 99 | package.json 100 | package-lock.json 101 | labels: | 102 | Type: Release 103 | assignees: ${{ github.actor }} 104 | draft: true 105 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - main 8 | types: 9 | - closed 10 | workflow_dispatch: 11 | inputs: 12 | version: 13 | description: 'Version to publish (e.g., 1.2.3)' 14 | required: false 15 | type: string 16 | 17 | jobs: 18 | release: 19 | if: | 20 | (github.event_name == 'pull_request' && 21 | github.event.pull_request.merged == true && 22 | contains(github.event.pull_request.labels.*.name, 'Type: Release')) || 23 | github.event_name == 'workflow_dispatch' 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: write 27 | id-token: write # OIDC 28 | pull-requests: write # PR comment 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 32 | with: 33 | persist-credentials: false 34 | 35 | - name: Get package info 36 | id: package 37 | run: | 38 | if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$INPUT_VERSION" ]; then 39 | VERSION="$INPUT_VERSION" 40 | else 41 | VERSION=$(jq -r '.version' package.json) 42 | fi 43 | PACKAGE_NAME=$(jq -r '.name' package.json) 44 | echo "version=$VERSION" >> $GITHUB_OUTPUT 45 | echo "name=$PACKAGE_NAME" >> $GITHUB_OUTPUT 46 | env: 47 | EVENT_NAME: ${{ github.event_name }} 48 | INPUT_VERSION: ${{ github.event.inputs.version }} 49 | 50 | - name: Check if tag exists 51 | id: tag-check 52 | run: | 53 | if git rev-parse "v$VERSION" >/dev/null 2>&1; then 54 | echo "exists=true" >> $GITHUB_OUTPUT 55 | else 56 | echo "exists=false" >> $GITHUB_OUTPUT 57 | fi 58 | env: 59 | VERSION: ${{ steps.package.outputs.version }} 60 | 61 | - name: Setup Node.js 62 | if: steps.tag-check.outputs.exists == 'false' 63 | uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 64 | with: 65 | node-version: 'lts/*' 66 | registry-url: 'https://registry.npmjs.org' 67 | 68 | - name: Ensure npm 11.5.1 or later is installed 69 | if: steps.tag-check.outputs.exists == 'false' 70 | run: | 71 | NPM_VERSION=$(npm -v) 72 | echo "Current npm version: $NPM_VERSION" 73 | if ! npx semver -r ">=11.5.1" "$NPM_VERSION"; then 74 | echo "npm version $NPM_VERSION is too old. Installing latest npm..." 75 | npm install -g npm@latest 76 | echo "Updated npm version: $(npm -v)" 77 | fi 78 | 79 | - name: Install dependencies 80 | if: steps.tag-check.outputs.exists == 'false' 81 | run: npm ci 82 | 83 | - name: Build package 84 | if: steps.tag-check.outputs.exists == 'false' 85 | run: npm run build 86 | 87 | - name: Publish to npm with provenance 88 | if: steps.tag-check.outputs.exists == 'false' 89 | run: npm publish --provenance --access public 90 | 91 | - name: Create GitHub Release with tag 92 | id: create-release 93 | if: steps.tag-check.outputs.exists == 'false' 94 | run: | 95 | if [ "$EVENT_NAME" = "workflow_dispatch" ]; then 96 | RELEASE_URL=$(gh release create "v$VERSION" \ 97 | --title "v$VERSION" \ 98 | --target "$SHA" \ 99 | --generate-notes) 100 | else 101 | RELEASE_URL=$(gh release create "v$VERSION" \ 102 | --title "v$VERSION" \ 103 | --target "$SHA" \ 104 | --notes "$PR_BODY") 105 | fi 106 | echo "url=$RELEASE_URL" >> $GITHUB_OUTPUT 107 | env: 108 | GH_TOKEN: ${{ github.token }} 109 | VERSION: ${{ steps.package.outputs.version }} 110 | SHA: ${{ github.sha }} 111 | EVENT_NAME: ${{ github.event_name }} 112 | PR_BODY: ${{ github.event.pull_request.body }} 113 | 114 | - name: Comment on PR - Success 115 | if: | 116 | always() && 117 | github.event_name == 'pull_request' && 118 | steps.tag-check.outputs.exists == 'false' && 119 | success() 120 | run: | 121 | gh pr comment "$PR_NUMBER" \ 122 | --body "✅ **Release v$VERSION completed successfully!** 123 | 124 | - 📦 npm package: https://www.npmjs.com/package/$PACKAGE_NAME/v/$VERSION 125 | - 🏷️ GitHub Release: $RELEASE_URL 126 | - 🔗 Workflow run: $SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID" 127 | env: 128 | GH_TOKEN: ${{ github.token }} 129 | PR_NUMBER: ${{ github.event.pull_request.number }} 130 | VERSION: ${{ steps.package.outputs.version }} 131 | PACKAGE_NAME: ${{ steps.package.outputs.name }} 132 | RELEASE_URL: ${{ steps.create-release.outputs.url }} 133 | SERVER_URL: ${{ github.server_url }} 134 | REPOSITORY: ${{ github.repository }} 135 | RUN_ID: ${{ github.run_id }} 136 | 137 | - name: Comment on PR - Failure 138 | if: | 139 | always() && 140 | github.event_name == 'pull_request' && 141 | steps.tag-check.outputs.exists == 'false' && 142 | failure() 143 | run: | 144 | gh pr comment "$PR_NUMBER" \ 145 | --body "❌ **Release v$VERSION failed** 146 | 147 | Please check the workflow logs for details. 148 | 🔗 Workflow run: $SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID" 149 | env: 150 | GH_TOKEN: ${{ github.token }} 151 | PR_NUMBER: ${{ github.event.pull_request.number }} 152 | VERSION: ${{ steps.package.outputs.version }} 153 | SERVER_URL: ${{ github.server_url }} 154 | REPOSITORY: ${{ github.repository }} 155 | RUN_ID: ${{ github.run_id }} 156 | --------------------------------------------------------------------------------