Repository: expo/expo
File: packages/@expo/cli/src/utils/npm.ts (TypeScript)

Code before:
import { IOSConfig } from '@expo/config-plugins';
import { JSONValue } from '@expo/json-file';
import spawnAsync from '@expo/spawn-async';
import { TarTypeFlag } from 'multitars';
import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';
import slugify from 'slugify';
import { Readable } from 'stream';

import { CommandError } from './errors';
import { extractStream } from './tar';
import { createCachedFetch } from '../api/rest/client';

const debug = require('debug')('expo:utils:npm') as typeof console.log;

const cachedFetch = createCachedFetch({
  cacheDirectory: 'template-cache',
  // Time to live. How long (in ms) responses remain cached before being automatically ejected. If undefined, responses are never automatically ejected from the cache.
  // ttl: 1000,
});

export function sanitizeNpmPackageName(name: string): string {
  // https://github.com/npm/validate-npm-package-name/#naming-rules
  return (
    applyKnownNpmPackageNameRules(name) ||
    applyKnownNpmPackageNameRules(slugify(name)) ||
    // If nothing is left use 'app' like we do in Xcode projects.
    'app'
  );
}

function applyKnownNpmPackageNameRules(name: string): string | null {
  // https://github.com/npm/validate-npm-package-name/#naming-rules

  // package name cannot start with '.' or '_'.
  while (/^(\.|_)/.test(name)) {
    name = name.substring(1);
  }

  name = name.toLowerCase().replace(/[^a-zA-Z._\-/@]/g, '');

  return (
    name
      // .replace(/![a-z0-9-._~]+/g, '')
      // Remove special characters

Review comment (bug):
The `cachedFetch` here isn't useful since the TTL is set to a second. This only really gives it enough time to fail the tests, which is likely happening due to locking issues, concurrency issues, read-while-write issues, writing failed streams, or a combination of these problems.

It's not very stream-safe or concurrency-safe and while this doesn't cause issues in the CLI, it keeps failing the tests, so I'm removing it. The low TTL means that this should have no negative consequences, and we didn't benefit from this cache anyway.

Code after:
import { IOSConfig } from '@expo/config-plugins';
import { JSONValue } from '@expo/json-file';
import spawnAsync from '@expo/spawn-async';
import { TarTypeFlag } from 'multitars';
import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';
import slugify from 'slugify';
import { Readable } from 'stream';

import { CommandError } from './errors';
import { extractStream } from './tar';

const debug = require('debug')('expo:utils:npm') as typeof console.log;

export function sanitizeNpmPackageName(name: string): string {
  // https://github.com/npm/validate-npm-package-name/#naming-rules
  return (
    applyKnownNpmPackageNameRules(name) ||
    applyKnownNpmPackageNameRules(slugify(name)) ||
    // If nothing is left use 'app' like we do in Xcode projects.
    'app'
  );
}

function applyKnownNpmPackageNameRules(name: string): string | null {
  // https://github.com/npm/validate-npm-package-name/#naming-rules

  // package name cannot start with '.' or '_'.
  while (/^(\.|_)/.test(name)) {
    name = name.substring(1);
  }

  name = name.toLowerCase().replace(/[^a-zA-Z._\-/@]/g, '');

  return (
    name
      // .replace(/![a-z0-9-._~]+/g, '')
      // Remove special characters
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '') || null
  );
}

export async function npmViewAsync(...props: string[]): Promise<JSONValue> {
  const cmd = ['view', ...props, '--json'];