xref: /expo/tools/src/Npm.ts (revision f2fbea2e)
1import fs from 'fs-extra';
2import glob from 'glob-promise';
3
4import { spawnAsync, spawnJSONCommandAsync } from './Utils';
5
6export const EXPO_DEVELOPERS_TEAM_NAME = 'expo:developers';
7
8export type PackageViewType = null | {
9  name: string;
10  version: string;
11  'dist-tags': {
12    latest: string;
13    [tag: string]: string;
14  };
15  versions: string[];
16  time: {
17    created: string;
18    modified: string;
19    [time: string]: string;
20  };
21  maintainers: string[];
22  description: string;
23  author: string;
24  gitHead: string;
25  dist: {
26    tarball: string;
27  };
28  // and more but these are the basic ones, we shouldn't need more.
29  [key: string]: unknown;
30};
31
32/**
33 * Runs `npm view` for package with given name. Returns null if package is not published yet.
34 */
35export async function getPackageViewAsync(
36  packageName: string,
37  version?: string
38): Promise<PackageViewType> {
39  try {
40    return await spawnJSONCommandAsync('npm', [
41      'view',
42      version ? `${packageName}@${version}` : packageName,
43      '--json',
44    ]);
45  } catch {
46    return null;
47  }
48}
49
50/**
51 * Download npm tarball
52 */
53export async function downloadPackageTarballAsync(
54  targetDir: string,
55  packageName: string,
56  version?: string
57): Promise<string> {
58  await fs.ensureDir(targetDir);
59  await spawnAsync('npm', ['pack', version ? `${packageName}@${version}` : packageName], {
60    cwd: targetDir,
61    stdio: 'ignore',
62  });
63  const result = await glob('*.tgz', { cwd: targetDir });
64  if (result.length === 0) {
65    throw new Error('Download tarball not found');
66  }
67  return result[0];
68}
69
70/**
71 * Publishes a package at given directory to the global npm registry.
72 */
73export async function publishPackageAsync(
74  packageDir: string,
75  tagName: string = 'latest',
76  dryRun: boolean = false
77): Promise<void> {
78  const args = ['publish', '--tag', tagName, '--access', 'public'];
79
80  if (dryRun) {
81    args.push('--dry-run');
82  }
83  await spawnAsync('npm', args, {
84    cwd: packageDir,
85  });
86}
87
88/**
89 * Adds dist-tag to a specific version of the package.
90 */
91export async function addTagAsync(
92  packageName: string,
93  version: string,
94  tagName: string
95): Promise<void> {
96  await spawnAsync('npm', ['dist-tag', 'add', `${packageName}@${version}`, tagName]);
97}
98
99/**
100 * Removes package's tag with given name.
101 */
102export async function removeTagAsync(packageName: string, tagName: string): Promise<void> {
103  await spawnAsync('npm', ['dist-tag', 'rm', packageName, tagName]);
104}
105
106/**
107 * Gets a list of user names in the team with given team name.
108 */
109export async function getTeamMembersAsync(teamName: string): Promise<string[]> {
110  return await spawnJSONCommandAsync('npm', ['team', 'ls', teamName, '--json']);
111}
112
113/**
114 * Adds a package to organization team granting access to everyone in the team.
115 */
116export async function grantReadWriteAccessAsync(
117  packageName: string,
118  teamName: string
119): Promise<void> {
120  await spawnAsync('npm', ['access', 'grant', 'read-write', teamName, packageName]);
121}
122
123/**
124 * Returns a name of the currently logged in user or `null` if logged out.
125 */
126export async function whoamiAsync(): Promise<string | null> {
127  try {
128    const { stdout } = await spawnAsync('npm', ['whoami']);
129    return stdout.trim();
130  } catch {
131    return null;
132  }
133}
134