• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const { join, resolve, basename, extname } = require('path')
3const fs = require('fs/promises')
4const localeCompare = require('@isaacs/string-locale-compare')('en')
5const docs = require('@npmcli/docs')
6
7const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
8const { definitions } = require('@npmcli/config/lib/definitions')
9const cmdList = require('../../lib/utils/cmd-list.js')
10const pkg = require('../../package.json')
11const { cleanCwd } = require('../fixtures/clean-snapshot.js')
12
13t.test('command list', async t => {
14  for (const [key, value] of Object.entries(cmdList)) {
15    t.matchSnapshot(value, key)
16  }
17})
18
19t.test('shorthands', async t => {
20  t.matchSnapshot(docs.shorthands(docs.TAGS.SHORTHANDS, {}), 'docs')
21})
22
23t.test('config', async t => {
24  const keys = Object.keys(definitions)
25  const flat = Object.entries(definitions).filter(([_, d]) => d.flatten).map(([k]) => k)
26  const notFlat = keys.filter(k => !flat.includes(k))
27  t.matchSnapshot(keys, 'all keys')
28  t.matchSnapshot(flat, 'keys that are flattened')
29  t.matchSnapshot(notFlat, 'keys that are not flattened')
30  t.matchSnapshot(docs.config(docs.TAGS.CONFIG, {}), 'all definitions')
31})
32
33t.test('flat options', async t => {
34  t.cleanSnapshot = (s) => cleanCwd(s)
35    .split(cleanCwd(process.execPath)).join('{NODE}')
36
37  const { npm } = await loadMockNpm(t, {
38    command: 'version',
39    exec: true,
40    npm: ({ other }) => ({
41      npmRoot: other,
42    }),
43    otherDirs: {
44      'package.json': JSON.stringify({ version: '1.1.1' }),
45    },
46    globals: {
47      'process.stdout.isTTY': false,
48      'process.stderr.isTTY': false,
49      'process.env': {
50        EDITOR: '{EDITOR}',
51        SHELL: '{SHELL}',
52      },
53      'process.version': '2.2.2',
54      'process.platform': '{PLATFORM}',
55      'process.arch': '{ARCH}',
56    },
57    mocks: {
58      'ci-info': { isCI: true, name: '{CI}' },
59      // currently the config version is read from npmRoot and the Npm
60      // constructor version is read from the root package.json file. This
61      // snapshot is meant to explicitly show that they are read from different
62      // places. In the future we should probably only read it from npmRoot,
63      // which would require more changes to ensure nothing depends on it being
64      // a static prop on the constructor
65      '{ROOT}/package.json': { version: '3.3.3' },
66    },
67  })
68
69  t.matchSnapshot(npm.flatOptions, 'full flat options object')
70})
71
72t.test('basic usage', async t => {
73  // snapshot basic usage without commands since all the command snapshots
74  // are generated in the following test
75  const { npm } = await loadMockNpm(t, {
76    mocks: {
77      '{LIB}/utils/cmd-list.js': { commands: [] },
78    },
79    config: { userconfig: '/some/config/file/.npmrc' },
80    globals: { process: { platform: 'posix' } },
81  })
82
83  t.cleanSnapshot = str => str
84    .replace(npm.npmRoot, '{BASEDIR}')
85    .replace(npm.config.get('userconfig'), '{USERCONFIG}')
86    .split(pkg.version).join('{VERSION}')
87
88  t.matchSnapshot(npm.usage)
89})
90
91t.test('usage', async t => {
92  const readdir = async (dir, ext) => {
93    const files = await fs.readdir(dir)
94    return files.filter(f => extname(f) === ext).map(f => basename(f, ext))
95  }
96
97  const fsCommands = await readdir(resolve(__dirname, '../../lib/commands'), '.js')
98  const docsCommands = await readdir(join(docs.paths.content, 'commands'), docs.DOC_EXT)
99  const bareCommands = ['npm', 'npx']
100
101  // XXX: These extra commands exist as js files but not as docs pages
102  const allDocs = docsCommands.concat(['get', 'set', 'll']).map(n => n.replace('npm-', ''))
103
104  // ensure that the list of js files in commands, docs files, and the command list
105  // are all in sync. eg, this will error if a command is removed but not its docs file
106  t.strictSame(
107    fsCommands.sort(localeCompare),
108    cmdList.commands,
109    'command list and fs are the same'
110  )
111  t.strictSame(
112    allDocs.filter(f => !bareCommands.includes(f)).sort(localeCompare),
113    cmdList.commands,
114    'command list and docs files are the same'
115  )
116
117  // use the list of files from the docs since those include the special cases
118  // for the bare npm and npx usage
119  for (const cmd of allDocs) {
120    t.test(cmd, async t => {
121      let output = null
122      if (!bareCommands.includes(cmd)) {
123        const mock = await loadMockNpm(t, { command: cmd })
124        output = mock[cmd].usage
125      }
126
127      const usage = docs.usage(docs.TAGS.USAGE, { path: cmd })
128      const params = docs.params(docs.TAGS.CONFIG, { path: cmd })
129        .split('\n')
130        .filter(l => l.startsWith('#### '))
131        .join('\n') || 'NO PARAMS'
132
133      t.matchSnapshot([output, usage, params].filter(Boolean).join('\n\n'))
134    })
135  }
136})
137