Skip to content

node:util shim missing styleText — breaks create-vite@9 and other Node 21+ scaffolders #16

@davideast

Description

@davideast

Summary

almostnode's node:util shim (src/shims/util.ts) doesn't export styleText. This is a Node.js 21.7+ API that several modern scaffolders depend on at module load — create-vite@9.0.7 is the one that surfaced this for me, but any tool wanting ANSI styling without manual escape codes is likely to use it.

The result is that running create-vite (and similar) inside almostnode fails immediately with:

TypeError: (0 , import_node_util.styleText) is not a function
  at /work/node_modules/create-vite/dist/index.js

Repro

import { createContainer } from "almostnode";

const c = createContainer();
c.vfs.mkdirSync("/work", { recursive: true });

await c.run("npm install create-vite@latest", { cwd: "/work" });
const r = await c.run(
  "node ./node_modules/create-vite/index.js . --template react-ts",
  { cwd: "/work" },
);
console.log(r); // exitCode: 1, stderr contains the TypeError above

I hit this driving an agent in the browser via piebox — the agent uses canonical npm create vite@latest . -- --template react-ts. That's overwhelmingly what's in LLM training data, so any agent using almostnode hits this on the most common scaffolding pattern.

Why this matters

create-vite, create-next-app, create-svelte, and other current-generation scaffolders are increasingly likely to use Node 21+ APIs. util.styleText is a small, isolated function, but its absence cascades because scaffolders use it at top-level for their CLI output. The module fails to even load.

Suggested fix

Add styleText to src/shims/util.ts. A minimal-but-correct implementation:

const ANSI_STYLES: Record<string, [number, number]> = {
  reset: [0, 0],
  bold: [1, 22],
  italic: [3, 23],
  underline: [4, 24],
  inverse: [7, 27],
  hidden: [8, 28],
  strikethrough: [9, 29],
  black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39],
  blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39],
  gray: [90, 39], grey: [90, 39],
  redBright: [91, 39], greenBright: [92, 39], yellowBright: [93, 39],
  blueBright: [94, 39], magentaBright: [95, 39], cyanBright: [96, 39], whiteBright: [97, 39],
  bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49],
  bgBlue: [44, 49], bgMagenta: [45, 49], bgCyan: [46, 49], bgWhite: [47, 49],
};

export function styleText(
  format: string | string[],
  text: string,
  options?: { validateStream?: boolean; stream?: unknown },
): string {
  const formats = Array.isArray(format) ? format : [format];
  // In the browser there's no TTY; per Node docs, when stream isn't a TTY,
  // styleText returns the raw text unchanged. Match that behavior so apps
  // that depend on `isTTY` checks don't get spurious escape codes.
  let opening = "";
  let closing = "";
  for (const name of formats) {
    const style = ANSI_STYLES[name];
    if (!style) continue;
    opening += \`\\u001b[\${style[0]}m\`;
    closing = \`\\u001b[\${style[1]}m\` + closing;
  }
  // Browser default: return raw (no TTY). If a consumer truly wants styling,
  // they can pass { stream: process.stdout } and we return styled — but the
  // shim doesn't expose a TTY-detect, so raw is the safe default.
  return text;
  // Alt impl if styling is desired in browser output: `return opening + text + closing;`
}

The Node docs (util.styleText) say styling is no-op'd when the stream isn't a TTY — that maps naturally to the browser environment, so returning raw text is the most faithful behavior and unblocks scaffolders that just want the function to exist.

Happy to send a PR if that's helpful.

Environment

  • almostnode 0.2.14
  • host: Chrome / Vite dev server
  • mode: createContainer() (trusted main-thread)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions