codekingpro/portable-devtools
115k
1
2const promzard = require('promzard')
3const path = require('path')
4const semver = require('semver')
5const { read } = require('read')
6const util = require('util')
7const PackageJson = require('@npmcli/package-json')
8
9const def = require.resolve('./default-input.js')
10
11const extras = [
12 'bundleDependencies',
13 'gypfile',
14 'serverjs',
15 'scriptpath',
16 'readme',
17 'bin',
18 'githead',
19 'fillTypes',
20 'normalizeData',
21]
22
23const isYes = (c) => !!(c.get('yes') || c.get('y') || c.get('force') || c.get('f'))
24
25const getConfig = (c) => {
26 // accept either a plain-jane object, or a config object with a "get" method.
27 if (typeof c.get !== 'function') {
28 const data = c
29 return {
30 get: (k) => data[k],
31 toJSON: () => data,
32 }
33 }
34 return c
35}
36
37// Coverage disabled because this is just walking back the fixPeople
38// normalization from the normalizeData step and we don't need to re-test all
39// of those paths.
40/* istanbul ignore next */
41const stringifyPerson = (p) => {
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}
47async function init (dir,
48 // TODO test for non-default definitions
49 /* istanbul ignore next */
50 input = def,
51 c = {}) {
52 const config = getConfig(c)
53 const yes = isYes(config)
54 const packageFile = path.resolve(dir, 'package.json')
55
56 // read what's already there to inform our prompts
57 const pkg = await PackageJson.load(dir, { create: true })
58 await pkg.normalize()
59
60 if (!semver.valid(pkg.content.version)) {
61 delete pkg.content.version
62 }
63
64 // make sure that the input is valid. if not, use the default
65 const pzData = await promzard(path.resolve(input), {
66 yes,
67 config,
68 filename: packageFile,
69 dirname: dir,
70 basename: path.basename(dir),
71 package: pkg.content,
72 }, { backupFile: def })
73
74 for (const [k, v] of Object.entries(pzData)) {
75 if (v != null) {
76 pkg.content[k] = v
77 }
78 }
79
80 await pkg.normalize({ steps: extras })
81
82 // turn the objects back into somewhat more humane strings.
83 // "normalizeData" does this and there isn't a way to choose which of those steps happen
84 if (pkg.content.author) {
85 pkg.content.author = stringifyPerson(pkg.content.author)
86 }
87
88 // no need for the readme now.
89 delete pkg.content.readme
90 delete pkg.content.readmeFilename
91
92 // really don't want to have this lying around in the file
93 delete pkg.content._id
94
95 // ditto
96 delete pkg.content.gitHead
97
98 // if the repo is empty, remove it.
99 if (!pkg.content.repository) {
100 delete pkg.content.repository
101 }
102
103 // readJson filters out empty descriptions, but init-package-json
104 // traditionally leaves them alone
105 if (!pkg.content.description) {
106 pkg.content.description = pzData.description
107 }
108
109 // optionalDependencies don't need to be repeated in two places
110 if (pkg.content.dependencies) {
111 if (pkg.content.optionalDependencies) {
112 for (const name of Object.keys(pkg.content.optionalDependencies)) {
113 delete pkg.content.dependencies[name]
114 }
115 }
116 if (Object.keys(pkg.content.dependencies).length === 0) {
117 delete pkg.content.dependencies
118 }
119 }
120
121 const stringified = JSON.stringify(pkg.content, null, 2) + '\n'
122 const msg = util.format('%s:\n\n%s\n', packageFile, stringified)
123
124 if (yes) {
125 await pkg.save()
126 if (!config.get('silent')) {
127 // eslint-disable-next-line no-console
128 console.log(`Wrote to ${msg}`)
129 }
130 return pkg.content
131 }
132
133 // eslint-disable-next-line no-console
134 console.log(`About to write to ${msg}`)
135 const ok = await read({ prompt: 'Is this OK? ', default: 'yes' })
136 if (!ok || !ok.toLowerCase().startsWith('y')) {
137 // eslint-disable-next-line no-console
138 console.log('Aborted.')
139 return
140 }
141
142 await pkg.save({ sort: true })
143 return pkg.content
144}
145
146module.exports = init
147module.exports.yes = isYes
148 