1 2const promzard = require('promzard') 3const path = require('path') 4const fs = require('fs/promises') 5const semver = require('semver') 6const read = require('read') 7const util = require('util') 8const rpj = require('read-package-json') 9 10const def = require.resolve('./default-input.js') 11 12// to validate the data object at the end as a worthwhile package 13// and assign default values for things. 14const _extraSet = rpj.extraSet 15const _rpj = util.promisify(rpj) 16const _rpjExtras = util.promisify(rpj.extras) 17const readPkgJson = async (file, pkg) => { 18 // only do a few of these. no need for mans or contributors if they're in the files 19 rpj.extraSet = _extraSet.filter(f => f.name !== 'authors' && f.name !== 'mans') 20 const p = pkg ? _rpjExtras(file, pkg) : _rpj(file) 21 return p.catch(() => ({})).finally(() => rpj.extraSet = _extraSet) 22} 23 24const isYes = (c) => !!(c.get('yes') || c.get('y') || c.get('force') || c.get('f')) 25 26const getConfig = (c = {}) => { 27 // accept either a plain-jane object, or a config object with a "get" method. 28 if (typeof c.get !== 'function') { 29 const data = c 30 return { 31 get: (k) => data[k], 32 toJSON: () => data, 33 } 34 } 35 return c 36} 37 38const stringifyPerson = (p) => { 39 if (typeof p === 'string') { 40 return p 41 } 42 const { name = '', url, web, email, mail } = p 43 const u = url || web 44 const e = email || mail 45 return `${name}${e ? ` <${e}>` : ''}${u ? ` (${u})` : ''}` 46} 47 48async function init (dir, input = def, c = {}) { 49 const config = getConfig(c) 50 const yes = isYes(config) 51 const packageFile = path.resolve(dir, 'package.json') 52 53 const pkg = await readPkgJson(packageFile) 54 55 if (!semver.valid(pkg.version)) { 56 delete pkg.version 57 } 58 59 // make sure that the input is valid. if not, use the default 60 const pzData = await promzard(path.resolve(input), { 61 yes, 62 config, 63 filename: packageFile, 64 dirname: path.dirname(packageFile), 65 basename: path.basename(path.dirname(packageFile)), 66 package: pkg, 67 }, { backupFile: def }) 68 69 for (const [k, v] of Object.entries(pzData)) { 70 if (v != null) { 71 pkg[k] = v 72 } 73 } 74 75 const pkgExtras = await readPkgJson(packageFile, pkg) 76 77 // turn the objects into somewhat more humane strings. 78 if (pkgExtras.author) { 79 pkgExtras.author = stringifyPerson(pkgExtras.author) 80 } 81 82 for (const set of ['maintainers', 'contributors']) { 83 if (Array.isArray(pkgExtras[set])) { 84 pkgExtras[set] = pkgExtras[set].map(stringifyPerson) 85 } 86 } 87 88 // no need for the readme now. 89 delete pkgExtras.readme 90 delete pkgExtras.readmeFilename 91 92 // really don't want to have this lying around in the file 93 delete pkgExtras._id 94 95 // ditto 96 delete pkgExtras.gitHead 97 98 // if the repo is empty, remove it. 99 if (!pkgExtras.repository) { 100 delete pkgExtras.repository 101 } 102 103 // readJson filters out empty descriptions, but init-package-json 104 // traditionally leaves them alone 105 if (!pkgExtras.description) { 106 pkgExtras.description = pzData.description 107 } 108 109 // optionalDependencies don't need to be repeated in two places 110 if (pkgExtras.dependencies) { 111 if (pkgExtras.optionalDependencies) { 112 for (const name of Object.keys(pkgExtras.optionalDependencies)) { 113 delete pkgExtras.dependencies[name] 114 } 115 } 116 if (Object.keys(pkgExtras.dependencies).length === 0) { 117 delete pkgExtras.dependencies 118 } 119 } 120 121 const stringified = JSON.stringify(pkgExtras, null, 2) + '\n' 122 const msg = util.format('%s:\n\n%s\n', packageFile, stringified) 123 const write = () => fs.writeFile(packageFile, stringified, 'utf8') 124 125 if (yes) { 126 await write() 127 if (!config.get('silent')) { 128 console.log(`Wrote to ${msg}`) 129 } 130 return pkgExtras 131 } 132 133 console.log(`About to write to ${msg}`) 134 const ok = await read({ prompt: 'Is this OK? ', default: 'yes' }) 135 if (!ok || !ok.toLowerCase().startsWith('y')) { 136 console.log('Aborted.') 137 return 138 } 139 140 await write() 141 return pkgExtras 142} 143 144module.exports = init 145module.exports.yes = isYes 146