From b4be3fdbebead5a1027200e88d16c8d2e7cf0f1a Mon Sep 17 00:00:00 2001 From: jdalton Date: Fri, 16 Oct 2026 12:19:25 -0400 Subject: [PATCH] fix(node): support Node 18.20.8 runtime --- .github/workflows/ci-gates.yml | 13 +++++++++++ package.json | 2 +- scripts/repo/package-exports.config.mts | 2 +- src/process/tree.mts | 6 ++++- src/strings/width.mts | 30 ++++++++++++++++--------- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-gates.yml b/.github/workflows/ci-gates.yml index b5ccfc398..1d21a58af 100644 --- a/.github/workflows/ci-gates.yml +++ b/.github/workflows/ci-gates.yml @@ -166,6 +166,19 @@ jobs: # The diff-aware suite escalates when it cannot resolve a safe scope. # Coverage shards verify the complete suite on each pushed commit. main-script: pnpm run test --ci + - name: Set up Node.js 18.20.8 + uses: ./.github/actions/fleet/setup + with: + node-version: '18.20.8' + - name: Verify the built width module on Node.js 18.20.8 + run: >- + node -e "const { stringWidth } = require('./dist/strings/width.js'); + const { findSelfNestedProcesses } = require('./dist/process/tree.js'); + const rows = [10, 20, 30, 40].map((pid, index) => ({ pid, ppid: + index === 0 ? 1 : pid - 10, command: 'wrapper' })); if + (stringWidth('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ') !== 2 || + findSelfNestedProcesses(rows, row => row.command).map(row => + row.pid).join(',') !== '40,30') process.exitCode = 1" cover-shards: if: github.event_name != 'pull_request' diff --git a/package.json b/package.json index 0c1ca0132..d3890f126 100644 --- a/package.json +++ b/package.json @@ -4336,7 +4336,7 @@ ] }, "engines": { - "node": ">=24", + "node": ">=18.20.8", "npm": ">=12.2.0", "pnpm": ">=12.12.0" }, diff --git a/scripts/repo/package-exports.config.mts b/scripts/repo/package-exports.config.mts index 7fe852177..5e8ad9d4c 100644 --- a/scripts/repo/package-exports.config.mts +++ b/scripts/repo/package-exports.config.mts @@ -202,7 +202,7 @@ export const config: ExportsConfig = { 'dist/**/shared-*.js', 'src/**', ], - nodeRange: '>=24', + nodeRange: '>=18.20.8', outDir: 'dist', publicNames: { './spinner/create': './spinner/spinner', diff --git a/src/process/tree.mts b/src/process/tree.mts index 5f1c48e4a..0b2cb49a9 100644 --- a/src/process/tree.mts +++ b/src/process/tree.mts @@ -15,6 +15,7 @@ */ import { getNodeChildProcess } from '../node/child-process.mjs' +import { arrayToSorted } from '../polyfills/array.mjs' export interface ProcessTreeRow { command: string @@ -59,7 +60,10 @@ export function findSelfNestedProcesses( } depthOf.set(pid, hops) } - return nested.toSorted((a, b) => depthOf.get(b.pid)! - depthOf.get(a.pid)!) + return arrayToSorted( + nested, + (a, b) => depthOf.get(b.pid)! - depthOf.get(a.pid)!, + ) } /** diff --git a/src/strings/width.mts b/src/strings/width.mts index 6ee9b0ecd..a3a8aef10 100644 --- a/src/strings/width.mts +++ b/src/strings/width.mts @@ -15,7 +15,7 @@ import { stripAnsi } from '../term/ansi/strip.mjs' import { IntlSegmenter } from '../primordials/intl.mjs' -import { RegExpPrototypeTest } from '../primordials/regexp.mjs' +import { RegExpCtor, RegExpPrototypeTest } from '../primordials/regexp.mjs' import { StringPrototypeCharCodeAt, StringPrototypeCodePointAt, @@ -70,21 +70,29 @@ let emojiRegex: RegExp try { // Try 'v' flag first (Node 20+) for most accurate Unicode property support. - zeroWidthClusterRegex = - /^(?:\p{Control}|\p{Default_Ignorable_Code_Point}|\p{Mark}|\p{Surrogate})+$/v + zeroWidthClusterRegex = new RegExpCtor( + '^(?:\\p{Control}|\\p{Default_Ignorable_Code_Point}|\\p{Mark}|\\p{Surrogate})+$', + 'v', + ) - leadingNonPrintingRegex = - /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v + leadingNonPrintingRegex = new RegExpCtor( + '^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+', + 'v', + ) - emojiRegex = /^\p{RGI_Emoji}$/v + emojiRegex = new RegExpCtor('^\\p{RGI_Emoji}$', 'v') /* c8 ignore start */ } catch { // Fall back to 'u' flag (Node 18+) โ€” no \p{Surrogate}, broader emoji. - zeroWidthClusterRegex = - /^(?:\p{Control}|\p{Default_Ignorable_Code_Point}|\p{Mark})+$/u - leadingNonPrintingRegex = - /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}]+/u - emojiRegex = /^\p{Extended_Pictographic}$/u + zeroWidthClusterRegex = new RegExpCtor( + '^(?:\\p{Control}|\\p{Default_Ignorable_Code_Point}|\\p{Mark})+$', + 'u', + ) + leadingNonPrintingRegex = new RegExpCtor( + '^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}]+', + 'u', + ) + emojiRegex = new RegExpCtor('^\\p{Extended_Pictographic}$', 'u') } /* c8 ignore stop */