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