Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
index.js297 linesDownload Raw Back to lib
1const { readFile, writeFile } = require('node:fs/promises')
2const { resolve } = require('node:path')
3const parseJSON = require('json-parse-even-better-errors')
4
5const updateDeps = require('./update-dependencies.js')
6const updateScripts = require('./update-scripts.js')
7const updateWorkspaces = require('./update-workspaces.js')
8const { normalize, syncNormalize } = require('./normalize.js')
9const { read, parse } = require('./read-package.js')
10const { packageSort } = require('./sort.js')
11
12// a list of handy specialized helper functions that take
13// care of special cases that are handled by the npm cli
14const knownSteps = new Set([
15  updateDeps,
16  updateScripts,
17  updateWorkspaces,
18])
19
20// list of all keys that are handled by "knownSteps" helpers
21const knownKeys = new Set([
22  ...updateDeps.knownKeys,
23  'scripts',
24  'workspaces',
25])
26
27class PackageJson {
28  // npm pkg fix
29  static fixSteps = Object.freeze([
30    'binRefs',
31    'bundleDependencies',
32    'fixName',
33    'fixVersionField',
34    'fixRepositoryField',
35    'fixDependencies',
36    'devDependencies',
37    'scriptpath',
38  ])
39
40  static normalizeSteps = Object.freeze([
41    '_id',
42    '_attributes',
43    'bundledDependencies',
44    'bundleDependencies',
45    'optionalDedupe',
46    'scripts',
47    'funding',
48    'bin',
49    'binDir',
50  ])
51
52  static prepareSteps = Object.freeze([
53    '_id',
54    '_attributes',
55    'bundledDependencies',
56    'bundleDependencies',
57    'bundleDependenciesDeleteFalse',
58    'gypfile',
59    'serverjs',
60    'scriptpath',
61    'authors',
62    'readme',
63    'mans',
64    'binDir',
65    'gitHead',
66    'fillTypes',
67    'normalizeData',
68    'binRefs',
69  ])
70
71  // create a new empty package.json, so we can save at the given path even
72  // though we didn't start from a parsed file
73  static async create (path, opts = {}) {
74    const p = new PackageJson()
75    await p.create(path)
76    if (opts.data) {
77      return p.update(opts.data)
78    }
79    return p
80  }
81
82  // Loads a package.json at given path and JSON parses
83  static async load (path, opts = {}) {
84    const p = new PackageJson()
85    // Avoid try/catch if we aren't going to create
86    if (!opts.create) {
87      return p.load(path)
88    }
89
90    try {
91      return await p.load(path)
92    } catch (err) {
93      if (!err.message.startsWith('Could not read package.json')) {
94        throw err
95      }
96      return await p.create(path)
97    }
98  }
99
100  // npm pkg fix
101  static async fix (path, opts) {
102    const p = new PackageJson()
103    await p.load(path, true)
104    return p.fix(opts)
105  }
106
107  // read-package-json compatible behavior
108  static async prepare (path, opts) {
109    const p = new PackageJson()
110    await p.load(path, true)
111    return p.prepare(opts)
112  }
113
114  // read-package-json-fast compatible behavior
115  static async normalize (path, opts) {
116    const p = new PackageJson()
117    await p.load(path)
118    return p.normalize(opts)
119  }
120
121  #path
122  #manifest
123  #readFileContent = ''
124  #canSave = true
125
126  // Load content from given path
127  async load (path, parseIndex) {
128    this.#path = path
129    let parseErr
130    try {
131      this.#readFileContent = await read(this.filename)
132    } catch (err) {
133      if (!parseIndex) {
134        throw err
135      }
136      parseErr = err
137    }
138
139    if (parseErr) {
140      const indexFile = resolve(this.path, 'index.js')
141      let indexFileContent
142      try {
143        indexFileContent = await readFile(indexFile, 'utf8')
144      } catch (err) {
145        throw parseErr
146      }
147      try {
148        this.fromComment(indexFileContent)
149      } catch (err) {
150        throw parseErr
151      }
152      // This wasn't a package.json so prevent saving
153      this.#canSave = false
154      return this
155    }
156
157    return this.fromJSON(this.#readFileContent)
158  }
159
160  // Load data from a JSON string/buffer
161  fromJSON (data) {
162    this.#manifest = parse(data)
163    return this
164  }
165
166  // Manually set data from an existing object
167  fromContent (data) {
168    if (!data || typeof data !== 'object') {
169      throw new Error('Content data must be an object')
170    }
171    this.#manifest = data
172    this.#canSave = false
173    return this
174  }
175
176  // Load data from a comment
177  // /**package { "name": "foo", "version": "1.2.3", ... } **/
178  fromComment (data) {
179    data = data.split(/^\/\*\*package(?:\s|$)/m)
180
181    if (data.length < 2) {
182      throw new Error('File has no package in comments')
183    }
184    data = data[1]
185    data = data.split(/\*\*\/$/m)
186
187    if (data.length < 2) {
188      throw new Error('File has no package in comments')
189    }
190    data = data[0]
191    data = data.replace(/^\s*\*/mg, '')
192
193    this.#manifest = parseJSON(data)
194    return this
195  }
196
197  get content () {
198    return this.#manifest
199  }
200
201  get path () {
202    return this.#path
203  }
204
205  get filename () {
206    if (this.path) {
207      return resolve(this.path, 'package.json')
208    }
209    return undefined
210  }
211
212  create (path) {
213    this.#path = path
214    this.#manifest = {}
215    return this
216  }
217
218  // This should be the ONLY way to set content in the manifest
219  update (content) {
220    if (!this.content) {
221      throw new Error('Can not update without content.  Please `load` or `create`')
222    }
223
224    for (const step of knownSteps) {
225      this.#manifest = step({ content, originalContent: this.content })
226    }
227
228    // unknown properties will just be overwritten
229    for (const [key, value] of Object.entries(content)) {
230      if (!knownKeys.has(key)) {
231        this.content[key] = value
232      }
233    }
234
235    return this
236  }
237
238  async save ({ sort } = {}) {
239    if (!this.#canSave) {
240      throw new Error('No package.json to save to')
241    }
242    const {
243      [Symbol.for('indent')]: indent,
244      [Symbol.for('newline')]: newline,
245      ...rest
246    } = this.content
247
248    const format = indent === undefined ? '  ' : indent
249    const eol = newline === undefined ? '\n' : newline
250
251    const content = sort ? packageSort(rest) : rest
252
253    const fileContent = `${
254      JSON.stringify(content, null, format)
255    }\n`
256      .replace(/\n/g, eol)
257
258    if (fileContent.trim() !== this.#readFileContent.trim()) {
259      const written = await writeFile(this.filename, fileContent)
260      this.#readFileContent = fileContent
261      return written
262    }
263  }
264
265  // steps is NOT overrideable here because this is a legacy function that's not being used in new places
266  syncNormalize (opts = {}) {
267    opts.steps = this.constructor.normalizeSteps.filter(s => s !== '_attributes')
268    syncNormalize(this, opts)
269    return this
270  }
271
272  async normalize (opts = {}) {
273    if (!opts.steps) {
274      opts.steps = this.constructor.normalizeSteps
275    }
276    await normalize(this, opts)
277    return this
278  }
279
280  async prepare (opts = {}) {
281    if (!opts.steps) {
282      opts.steps = this.constructor.prepareSteps
283    }
284    await normalize(this, opts)
285    return this
286  }
287
288  async fix (opts = {}) {
289    // This one is not overridable
290    opts.steps = this.constructor.fixSteps
291    await normalize(this, opts)
292    return this
293  }
294}
295
296module.exports = PackageJson
297 
codekingpro/portable-devtools · Team Ai