UNPKG

universal-emoji-parser

Version:

This tool allow parse unicode and emoji codes to html images using emojilib && Twemoji CDN

403 lines (284 loc) 14.3 kB
--- name: npm-publish-walkthrough description: Full release flow including GitHub Actions internals, manual fallback, and rollback strategies --- # Skill: `npm-publish-walkthrough` Comprehensive reference for releasing Universal Emoji Parser to npm. Covers the automated CI flow, the manual fallback, and what to do when things go wrong. For the day-to-day procedural commands, see [`/release-npm`](../commands/release-npm.md). This skill explains the full picture. ## The release model Universal Emoji Parser uses a **merge-to-main = release** model: 1. Every PR merge to `main` triggers `release_and_publish.yml` 2. The workflow patch-bumps the version, builds, tags, creates a GitHub Release, and publishes to npm 3. Humans don't run `npm version` or `npm publish` manually under normal conditions Tradeoffs: - ✅ Every change ships immediately — no batching, no delayed release - ✅ Release notes are automatic - ✅ Patch number reflects merge count - ❌ Can't easily ship a major version (the workflow auto-runs `npm version patch`) - ❌ Unreviewable releases — by the time you see version X, it's already on npm ## The CI workflow in detail `.github/workflows/release_and_publish.yml`. Trigger: ```yaml on: pull_request: branches: [main] types: [closed] ``` …with `if: github.event.pull_request.merged == true` on every job (so closing-without-merging doesn't release). ### Jobs in order #### 1. `check_pr_size_label` Reads PR labels, looks for `Size - XS / S / M / L / XL / XXL` (set earlier by `pull_request_check.yml`), emits a colored emoji indicator. Pure metadata for the channel notification. #### 2. `notify_on_channel_start` Posts "deployment started" to a DailyBot Slack-like channel via: ``` POST https://api.dailybot.com/v1/send-message/ Headers: X-API-KEY: ${{ secrets.DAILYBOT_API_KEY }} ``` Includes PR number, title, body, size label, workflow URL. #### 3. `deploy_setup` ```yaml - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '24' } - uses: actions/cache@v4 with: path: | ~/.npm node_modules key: ${{ runner.os }}-build-cache-node-modules-${{ hashFiles('**/package-lock.json') }} - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} run: npm install ``` Cache key uses `package-lock.json`'s hash — but we don't commit `package-lock.json`. So the hash is empty/missing, and the cache key collapses to `${{ runner.os }}-build-cache-node-modules-`. This effectively means "always cache hits across all runs that share the OS." Good for speed; bad if a security advisory requires invalidating. To force a fresh install, bump the cache key (e.g., add `-v2`). #### 4. `deploy_validate_linters_and_code_format` ```yaml - run: npm run eslint:check - run: npm run prettier:check ``` Hard gate. Both must pass. #### 5. `deploy_tests` ```yaml - run: npm run test ``` All Mocha specs must pass. The regenerator test (`prepareEmojiLibJson.test.ts`) is `it.skip`'d so it doesn't run. #### 6. `build` ```yaml - run: | npm run build if [ ! -d "dist" ]; then echo "⚠️ Error: dist folder does not exist." exit 1 fi ``` Webpack production build. The output (`dist/`) is cached for the publish job. > **Gotcha**: this step doesn't run `npm run build:tsc`. Without it, `dist/index.d.ts` may be missing from the published tarball. If consumers report "no types," the fix is to add `npm run build:tsc` here. #### 7. `release_and_publish` The actual release. Steps: ```yaml - uses: actions/checkout@v4 with: fetch-depth: '30' # need history for release notes token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} - uses: actions/setup-node@v4 with: node-version: '24' registry-url: https://registry.npmjs.org/ - run: | git config user.name "🤖 DailyBot" git config user.email "ops@dailybot.com" - run: | bash .github/scripts/get_github_release_log.sh if [[ ! -f git_logs_output.txt ]]; then echo "⚠️ No description found for release body content." exit 1 fi - run: | npm run release # npm version patch -m "[🤖 DailyBot] New release to v%s launched 🚀" git push --follow-tags origin main - run: | GITHUB_RELEASE_TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) if [[ -z $GITHUB_RELEASE_TAG ]]; then echo "⚠️ No release tag found." exit 1 fi echo "::set-env name=GITHUB_RELEASE_TAG::$GITHUB_RELEASE_TAG" env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true - uses: ncipollo/release-action@v1 with: name: Release ${{ env.GITHUB_RELEASE_TAG }} tag: ${{ env.GITHUB_RELEASE_TAG }} bodyFile: git_logs_output.txt token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} - run: | if [ ! -d "dist" ]; then echo "⚠️ Error: dist folder does not exist." exit 1 fi npm publish echo "package_version=$(npm pkg get version)" >> $GITHUB_OUTPUT env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | PR_MERGED=$(jq --raw-output .pull_request.merged "$GITHUB_EVENT_PATH") if [ "$PR_MERGED" = "true" ]; then git push origin --delete "${{ github.event.pull_request.head.ref }}" fi ``` Note the `ACTIONS_ALLOW_UNSECURE_COMMANDS: true` and `::set-env`. These are deprecated GitHub Actions syntax (set-env was disabled by default in 2020). The workflow predates the modern alternative (`echo "TAG=..." >> $GITHUB_ENV`). It still works because of the explicit `ACTIONS_ALLOW_UNSECURE_COMMANDS` toggle, but it's a tech-debt item — eventually GitHub may remove it. When that happens, fix the syntax: ```bash echo "GITHUB_RELEASE_TAG=$GITHUB_RELEASE_TAG" >> $GITHUB_ENV ``` #### 8. `cleanup_caches` Triggers `cleanup_caches.yml` via `repository_dispatch` to GC stale GHA caches. #### 9. `notify_on_channel_end` `if: always()` — runs even if earlier jobs failed. Posts the per-job status (✅/❌/⏩/❓) plus overall success/failure to the DailyBot channel. On failure, includes `vars.USERS_TO_NOTIFY` (a Slack mention list). ## The release notes script `.github/scripts/get_github_release_log.sh`: ```bash git log --pretty=oneline | sed 's/[^ ]* *//' > git_logs.txt while read text_line; do if [[ "$text_line" =~ "[🤖 DailyBot] New release to v" ]]; then break fi if [[ ! "$text_line" =~ "Merge branch 'main'" ]] && [[ ! "$text_line" =~ "Merge pull request" ]]; then echo "🚩 $text_line" >> git_logs_output.txt fi done < git_logs.txt ``` Walks `git log` from HEAD until the previous `[🤖 DailyBot] New release to v` commit, collects everything in between (skipping merge commits), prefixes each with `🚩`. The result is the body of the GitHub Release. Implications: - **The bot's commit message format is the boundary marker** — if you change it, you must also change the regex in this script - **Release notes are commit-message-quality** — write good commit messages; they become the changelog - **Merges are filtered** — if your repo's merge style is "Squash and merge," the squash commit's message is what's recorded (good); if "Create a merge commit," the merge message is filtered (so the underlying commits are preserved as long as they're not squashed away) ## The `npm version` step ```bash npm version patch -m "[🤖 DailyBot] New release to v%s launched 🚀" ``` What this does: 1. Reads current version from `package.json` 2. Increments the patch number (e.g., `2.0.79``2.0.80`) 3. Writes the new version back to `package.json` 4. Creates a git commit: `[🤖 DailyBot] New release to v2.0.80 launched 🚀` 5. Creates a git tag: `v2.0.80` If `npm version` fails (working tree dirty, network issue), the workflow aborts and no release happens. ### Why patch only `npm run release` is hardcoded to patch: ```json "release": "npm version patch -m \"[🤖 DailyBot] New release to v%s launched 🚀\"" ``` For minor or major releases, you have two options: **Option A: Manually pre-bump in the PR** In the PR that triggers a minor release: ```bash # In your PR branch, before final review $EDITOR package.json # change version from 2.0.79 to 2.1.0 git commit -am "chore: bump version to 2.1.0 for release" git push # Merge the PR # Workflow runs `npm version patch`, bumps 2.1.0 → 2.1.1 # Final published version is 2.1.1, not 2.1.0 ``` That last detail (`2.1.1`, not `2.1.0`) means the "minor" version users see is the `.1` patch — minor enough not to matter for semver, awkward for release notes. **Option B: Disable auto-bump for the release** Edit the workflow temporarily to skip `npm run release`, run `npm version minor` locally, push the tag, re-enable the workflow. More involved. Neither is great. A future improvement: add a `[skip auto-bump]` PR-title convention or a `release-type: minor` PR label that the workflow honors. ## Manual release procedure Required when: CI is down, the workflow is broken, an emergency hotfix needs to ship in minutes. See [`/release-npm`](../commands/release-npm.md) for the procedural walkthrough. Key points: 1. **Always run the full check sequence** — never publish unverified 2. **Use `npm version`** to bump + commit + tag atomically (don't edit `package.json` by hand) 3. **`git push --follow-tags`** — pushing without `--follow-tags` leaves the tag local 4. **`npm publish`** requires `npm login` or `NODE_AUTH_TOKEN` env var 5. **Smoke-test in a fresh directory** after publish — `npm install` the published version and verify ## Rollback strategies ### Within 72 hours of publish, with no downloads ```bash npm unpublish universal-emoji-parser@<bad-version> --force ``` This is the only way to make the bad version disappear from npm. Limited window. ### After 72 hours You can't unpublish. Two options: **Deprecate**: ```bash npm deprecate universal-emoji-parser@<bad-version> "Broken; use <good-version>" ``` Adds a console warning when consumers install the deprecated version. Doesn't block install. **Publish a fix**: Bump again (usually patch) with the fix and ship. Consumers `npm update` to get the fix. For severe issues (security, malware in a dep), npm support can intervene faster — open a ticket. ## Common failure modes ### `npm publish` 401 Unauthorized - `secrets.NPM_TOKEN` is expired - Token is account-wide but doesn't have the new package's name in its allowlist - Account 2FA is `auth-and-writes` mode and the automation token can't satisfy it (use an automation-type token specifically — npm distinguishes) Fix: regenerate the token in npm settings → Profile → Access Tokens → Generate New Token → "Automation". Update `secrets.NPM_TOKEN` in GitHub repo settings. ### `npm version` says "Git working directory not clean" Some prior step modified the repo. Common causes: - A test wrote to a file in the repo (regenerator, sloppy test) - `dist/` was modified between cache restore and `npm version` step - `package-lock.json` was generated by `npm install` and isn't in `.gitignore` Fix: add the offending file to `.gitignore`, or `git checkout -- .` before `npm version`. ### `git push --follow-tags` rejected Branch protection on `main` requires PRs, blocking direct pushes. The workflow needs a way through: - Use a fine-grained PAT or GitHub App token with **bypass branch protection** privileges - Or weaken branch protection for the bot user - Or change the workflow to not push directly (push to a release branch, then merge — much more complex) ### `actions/cache` cache miss every run If the cache key uses `hashFiles('**/package-lock.json')` but you don't commit the lockfile, the hash is empty → cache key is constant → caches collide. This usually still works (collisions are valid hits), but if you ever need to force-refresh, bump the cache key suffix: ```yaml key: ${{ runner.os }}-build-cache-node-modules-v2-${{ hashFiles('**/package-lock.json') }} ``` ## What gets published `.npmignore` controls the tarball: ``` Excluded: .vscode_example, .vscode, .devcontainer_example, .devcontainer src, test .babelrc, .env, .env_example .gitignore, .travis.yml package-lock.json tsconfig.json, webpack.config.js docker, .github eslint.config.mjs (flat ESLint; `.eslintignore` removed — use `ignores` in config) get_github_release_log.sh git_logs.txt, git_logs_output.txt .editorconfig, .prettierrc Included by default (everything not excluded): dist/index.js dist/index.d.ts dist/lib/type.d.ts dist/*.map package.json README.md LICENSE ``` Verify before publishing: ```bash npm pack --dry-run ``` If the output includes `src/`, `test/`, or config files, fix `.npmignore`. ## Side effect: branch deletion The final step of `release_and_publish.yml`: ```bash git push origin --delete "${{ github.event.pull_request.head.ref }}" ``` Deletes the source branch of the merged PR. This is convenient (keeps the branch list clean) but **irreversible** — you can't recover the branch from GitHub UI after this. If a developer wants to keep their branch (e.g., for follow-up PRs), they have to either: - Re-push the branch after the release runs - Make a backup tag before merging This isn't documented prominently anywhere — be prepared for the question. ## Notifications The workflow posts to a DailyBot Slack-like channel using: - `secrets.DAILYBOT_API_KEY` — auth header - `vars.DAILYBOT_DEPLOYMENT_NOTIFICATION_CHANNEL` — channel ID - `vars.DAILYBOT_WORKFLOWS_NOTIFICATION_CHANNEL` — channel ID for `check_packages_versions.yml` - `vars.USERS_TO_NOTIFY` — Slack mention list for failures If you fork and don't use DailyBot, either: 1. Replace the curl calls with your own webhook (Slack, Discord, Teams) 2. Strip the `notify_on_channel_*` jobs and dependencies entirely Without the secrets/vars, the curl calls fail silently — the rest of the pipeline still works, but you lose visibility. ## Why this is over-engineered for a small library Honest answer: it isn't, for DailyBot's organizational needs. The size labels, the channel notifications, the auto-merge of dep PRs — these reflect a pattern DailyBot uses across many small libraries. Forking this for a different organization will likely simplify. For a fork, see [Fork Customization → Step 5](../../docs/FORK_CUSTOMIZATION.md#step-5--ci-release-workflows).