1const libnpmversion = require('libnpmversion')
2const { resolve } = require('path')
3const { promisify } = require('util')
4const readFile = promisify(require('fs').readFile)
5
6const updateWorkspaces = require('../workspaces/update-workspaces.js')
7const BaseCommand = require('../base-command.js')
8
9class Version extends BaseCommand {
10  static description = 'Bump a package version'
11  static name = 'version'
12  static params = [
13    'allow-same-version',
14    'commit-hooks',
15    'git-tag-version',
16    'json',
17    'preid',
18    'sign-git-tag',
19    'workspace',
20    'workspaces',
21    'workspaces-update',
22    'include-workspace-root',
23  ]
24
25  static workspaces = true
26  static ignoreImplicitWorkspace = false
27
28  /* eslint-disable-next-line max-len */
29  static usage = ['[<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]']
30
31  static async completion (opts) {
32    const {
33      conf: {
34        argv: { remain },
35      },
36    } = opts
37    if (remain.length > 2) {
38      return []
39    }
40
41    return [
42      'major',
43      'minor',
44      'patch',
45      'premajor',
46      'preminor',
47      'prepatch',
48      'prerelease',
49      'from-git',
50    ]
51  }
52
53  async exec (args) {
54    switch (args.length) {
55      case 0:
56        return this.list()
57      case 1:
58        return this.change(args)
59      default:
60        throw this.usageError()
61    }
62  }
63
64  async execWorkspaces (args) {
65    switch (args.length) {
66      case 0:
67        return this.listWorkspaces()
68      case 1:
69        return this.changeWorkspaces(args)
70      default:
71        throw this.usageError()
72    }
73  }
74
75  async change (args) {
76    const prefix = this.npm.config.get('tag-version-prefix')
77    const version = await libnpmversion(args[0], {
78      ...this.npm.flatOptions,
79      path: this.npm.prefix,
80    })
81    return this.npm.output(`${prefix}${version}`)
82  }
83
84  async changeWorkspaces (args) {
85    const prefix = this.npm.config.get('tag-version-prefix')
86    await this.setWorkspaces()
87    const updatedWorkspaces = []
88    for (const [name, path] of this.workspaces) {
89      this.npm.output(name)
90      const version = await libnpmversion(args[0], {
91        ...this.npm.flatOptions,
92        'git-tag-version': false,
93        path,
94      })
95      updatedWorkspaces.push(name)
96      this.npm.output(`${prefix}${version}`)
97    }
98    return this.update(updatedWorkspaces)
99  }
100
101  async list (results = {}) {
102    const pj = resolve(this.npm.prefix, 'package.json')
103
104    const pkg = await readFile(pj, 'utf8')
105      .then(data => JSON.parse(data))
106      .catch(() => ({}))
107
108    if (pkg.name && pkg.version) {
109      results[pkg.name] = pkg.version
110    }
111
112    results.npm = this.npm.version
113    for (const [key, version] of Object.entries(process.versions)) {
114      results[key] = version
115    }
116
117    if (this.npm.config.get('json')) {
118      this.npm.output(JSON.stringify(results, null, 2))
119    } else {
120      this.npm.output(results)
121    }
122  }
123
124  async listWorkspaces () {
125    const results = {}
126    await this.setWorkspaces()
127    for (const path of this.workspacePaths) {
128      const pj = resolve(path, 'package.json')
129      // setWorkspaces has already parsed package.json so we know it won't error
130      const pkg = await readFile(pj, 'utf8').then(data => JSON.parse(data))
131
132      if (pkg.name && pkg.version) {
133        results[pkg.name] = pkg.version
134      }
135    }
136    return this.list(results)
137  }
138
139  async update (workspaces) {
140    const {
141      config,
142      flatOptions,
143      localPrefix,
144    } = this.npm
145
146    await updateWorkspaces({
147      config,
148      flatOptions,
149      localPrefix,
150      npm: this.npm,
151      workspaces,
152    })
153  }
154}
155
156module.exports = Version
157