• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* globals config, dirname, package, basename, yes, prompt */
2
3const fs = require('fs/promises')
4const path = require('path')
5const validateLicense = require('validate-npm-package-license')
6const validateName = require('validate-npm-package-name')
7const npa = require('npm-package-arg')
8const semver = require('semver')
9
10// more popular packages should go here, maybe?
11const isTestPkg = (p) => !!p.match(/^(expresso|mocha|tap|coffee-script|coco|streamline)$/)
12
13const invalid = (msg) => Object.assign(new Error(msg), { notValid: true })
14
15const readDeps = (test, excluded) => async () => {
16  const dirs = await fs.readdir('node_modules').catch(() => null)
17
18  if (!dirs) {
19    return
20  }
21
22  const deps = {}
23  for (const dir of dirs) {
24    if (dir.match(/^\./) || test !== isTestPkg(dir) || excluded[dir]) {
25      continue
26    }
27
28    const dp = path.join(dirname, 'node_modules', dir, 'package.json')
29    const p = await fs.readFile(dp, 'utf8').then((d) => JSON.parse(d)).catch(() => null)
30
31    if (!p || !p.version || p?._requiredBy?.some((r) => r === '#USER')) {
32      continue
33    }
34
35    deps[dir] = config.get('save-exact') ? p.version : config.get('save-prefix') + p.version
36  }
37
38  return deps
39}
40
41const getConfig = (key) => {
42  // dots take precedence over dashes
43  const def = config?.defaults?.[`init.${key}`]
44  const val = config.get(`init.${key}`)
45  return (val !== def && val) ? val : config.get(`init-${key.replace(/\./g, '-')}`)
46}
47
48const getName = () => {
49  const rawName = package.name || basename
50  let name = rawName
51    .replace(/^node-|[.-]js$/g, '')
52    .replace(/\s+/g, ' ')
53    .replace(/ /g, '-')
54    .toLowerCase()
55
56  let spec
57  try {
58    spec = npa(name)
59  } catch {
60    spec = {}
61  }
62
63  let scope = config.get('scope')
64
65  if (scope) {
66    if (scope.charAt(0) !== '@') {
67      scope = '@' + scope
68    }
69    if (spec.scope) {
70      name = scope + '/' + spec.name.split('/')[1]
71    } else {
72      name = scope + '/' + name
73    }
74  }
75
76  return name
77}
78
79const name = getName()
80exports.name = yes ? name : prompt('package name', name, (data) => {
81  const its = validateName(data)
82  if (its.validForNewPackages) {
83    return data
84  }
85  const errors = (its.errors || []).concat(its.warnings || [])
86  return invalid(`Sorry, ${errors.join(' and ')}.`)
87})
88
89const version = package.version || getConfig('version') || '1.0.0'
90exports.version = yes ? version : prompt('version', version, (v) => {
91  if (semver.valid(v)) {
92    return v
93  }
94  return invalid(`Invalid version: "${v}"`)
95})
96
97if (!package.description) {
98  exports.description = yes ? '' : prompt('description')
99}
100
101if (!package.main) {
102  exports.main = async () => {
103    const files = await fs.readdir(dirname)
104      .then(list => list.filter((f) => f.match(/\.js$/)))
105      .catch(() => [])
106
107    let index
108    if (files.includes('index.js')) {
109      index = 'index.js'
110    } else if (files.includes('main.js')) {
111      index = 'main.js'
112    } else if (files.includes(basename + '.js')) {
113      index = basename + '.js'
114    } else {
115      index = files[0] || 'index.js'
116    }
117
118    return yes ? index : prompt('entry point', index)
119  }
120}
121
122if (!package.bin) {
123  exports.bin = async () => {
124    try {
125      const d = await fs.readdir(path.resolve(dirname, 'bin'))
126      // just take the first js file we find there, or nada
127      let r = d.find(f => f.match(/\.js$/))
128      if (r) {
129        r = `bin/${r}`
130      }
131      return r
132    } catch {
133      // no bins
134    }
135  }
136}
137
138exports.directories = async () => {
139  const dirs = await fs.readdir(dirname)
140
141  const res = dirs.reduce((acc, d) => {
142    if (/^examples?$/.test(d)) {
143      acc.example = d
144    } else if (/^tests?$/.test(d)) {
145      acc.test = d
146    } else if (/^docs?$/.test(d)) {
147      acc.doc = d
148    } else if (d === 'man') {
149      acc.man = d
150    } else if (d === 'lib') {
151      acc.lib = d
152    }
153
154    return acc
155  }, {})
156
157  return Object.keys(res).length === 0 ? undefined : res
158}
159
160if (!package.dependencies) {
161  exports.dependencies = readDeps(false, package.devDependencies || {})
162}
163
164if (!package.devDependencies) {
165  exports.devDependencies = readDeps(true, package.dependencies || {})
166}
167
168// MUST have a test script!
169if (!package.scripts) {
170  const scripts = package.scripts || {}
171  const notest = 'echo "Error: no test specified" && exit 1'
172  exports.scripts = async () => {
173    const d = await fs.readdir(path.join(dirname, 'node_modules')).catch(() => [])
174
175    // check to see what framework is in use, if any
176    let command
177    if (!scripts.test || scripts.test === notest) {
178      const commands = {
179        tap: 'tap test/*.js',
180        expresso: 'expresso test',
181        mocha: 'mocha',
182      }
183      for (const [k, v] of Object.entries(commands)) {
184        if (d.includes(k)) {
185          command = v
186        }
187      }
188    }
189
190    const promptArgs = ['test command', (t) => t || notest]
191    if (command) {
192      promptArgs.splice(1, 0, command)
193    }
194    scripts.test = yes ? command || notest : prompt(...promptArgs)
195
196    return scripts
197  }
198}
199
200if (!package.repository) {
201  exports.repository = async () => {
202    const gconf = await fs.readFile('.git/config', 'utf8').catch(() => '')
203    const lines = gconf.split(/\r?\n/)
204
205    let url
206    const i = lines.indexOf('[remote "origin"]')
207
208    if (i !== -1) {
209      url = gconf[i + 1]
210      if (!url.match(/^\s*url =/)) {
211        url = gconf[i + 2]
212      }
213      if (!url.match(/^\s*url =/)) {
214        url = null
215      } else {
216        url = url.replace(/^\s*url = /, '')
217      }
218    }
219
220    if (url && url.match(/^git@github.com:/)) {
221      url = url.replace(/^git@github.com:/, 'https://github.com/')
222    }
223
224    return yes ? url || '' : prompt('git repository', url || undefined)
225  }
226}
227
228if (!package.keywords) {
229  exports.keywords = yes ? '' : prompt('keywords', (data) => {
230    if (!data) {
231      return
232    }
233    if (Array.isArray(data)) {
234      data = data.join(' ')
235    }
236    if (typeof data !== 'string') {
237      return data
238    }
239    return data.split(/[\s,]+/)
240  })
241}
242
243if (!package.author) {
244  const authorName = getConfig('author.name')
245  exports.author = authorName
246    ? {
247      name: authorName,
248      email: getConfig('author.email'),
249      url: getConfig('author.url'),
250    }
251    : yes ? '' : prompt('author')
252}
253
254const license = package.license || getConfig('license') || 'ISC'
255exports.license = yes ? license : prompt('license', license, (data) => {
256  const its = validateLicense(data)
257  if (its.validForNewPackages) {
258    return data
259  }
260  const errors = (its.errors || []).concat(its.warnings || [])
261  return invalid(`Sorry, ${errors.join(' and ')}.`)
262})
263