1- const fs = require ( 'fs' )
2- const promisify = require ( 'util' ) . promisify
3- const readFile = promisify ( fs . readFile )
4- const writeFile = promisify ( fs . writeFile )
1+ const { readFile, writeFile } = require ( 'fs/promises' )
52const { resolve } = require ( 'path' )
63const updateDeps = require ( './update-dependencies.js' )
74const updateScripts = require ( './update-scripts.js' )
85const updateWorkspaces = require ( './update-workspaces.js' )
6+ const normalize = require ( './normalize.js' )
97
108const parseJSON = require ( 'json-parse-even-better-errors' )
119
12- const _filename = Symbol ( 'filename' )
13- const _manifest = Symbol ( 'manifest' )
14- const _readFileContent = Symbol ( 'readFileContent' )
15-
1610// a list of handy specialized helper functions that take
1711// care of special cases that are handled by the npm cli
1812const knownSteps = new Set ( [
@@ -29,42 +23,111 @@ const knownKeys = new Set([
2923] )
3024
3125class PackageJson {
26+ static normalizeSteps = Object . freeze ( [
27+ '_id' ,
28+ '_attributes' ,
29+ 'bundledDependencies' ,
30+ 'bundleDependencies' ,
31+ 'optionalDedupe' ,
32+ 'scripts' ,
33+ 'funding' ,
34+ 'bin' ,
35+ ] )
36+
37+ static prepareSteps = Object . freeze ( [
38+ '_attributes' ,
39+ 'bundledDependencies' ,
40+ 'bundleDependencies' ,
41+ 'gypfile' ,
42+ 'serverjs' ,
43+ 'scriptpath' ,
44+ 'authors' ,
45+ 'readme' ,
46+ 'mans' ,
47+ 'binDir' ,
48+ 'gitHead' ,
49+ 'fillTypes' ,
50+ 'normalizeData' ,
51+ 'binRefs' ,
52+ ] )
53+
54+ // default behavior, just loads and parses
3255 static async load ( path ) {
3356 return await new PackageJson ( path ) . load ( )
3457 }
3558
59+ // read-package-json compatible behavior
60+ static async prepare ( path , opts ) {
61+ return await new PackageJson ( path ) . prepare ( opts )
62+ }
63+
64+ // read-package-json-fast compatible behavior
65+ static async normalize ( path , opts ) {
66+ return await new PackageJson ( path ) . normalize ( opts )
67+ }
68+
69+ #filename
70+ #path
71+ #manifest = { }
72+ #readFileContent = ''
73+ #fromIndex = false
74+
3675 constructor ( path ) {
37- this [ _filename ] = resolve ( path , 'package.json' )
38- this [ _manifest ] = { }
39- this [ _readFileContent ] = ''
76+ this . #path = path
77+ this . #filename = resolve ( path , 'package.json' )
4078 }
4179
42- async load ( ) {
80+ async load ( parseIndex ) {
81+ let parseErr
4382 try {
44- this [ _readFileContent ] =
45- await readFile ( this [ _filename ] , 'utf8' )
83+ this . #readFileContent =
84+ await readFile ( this . #filename , 'utf8' )
4685 } catch ( err ) {
47- throw new Error ( 'package.json not found' )
86+ err . message = `Could not read package.json: ${ err } `
87+ if ( ! parseIndex ) {
88+ throw err
89+ }
90+ parseErr = err
91+ }
92+
93+ if ( parseErr ) {
94+ const indexFile = resolve ( this . #path, 'index.js' )
95+ let indexFileContent
96+ try {
97+ indexFileContent = await readFile ( indexFile , 'utf8' )
98+ } catch ( err ) {
99+ throw parseErr
100+ }
101+ try {
102+ this . #manifest = fromComment ( indexFileContent )
103+ } catch ( err ) {
104+ throw parseErr
105+ }
106+ this . #fromIndex = true
107+ return this
48108 }
49109
50110 try {
51- this [ _manifest ] =
52- parseJSON ( this [ _readFileContent ] )
111+ this . #manifest = parseJSON ( this . #readFileContent)
53112 } catch ( err ) {
54- throw new Error ( `Invalid package.json: ${ err } ` )
113+ err . message = `Invalid package.json: ${ err } `
114+ throw err
55115 }
56-
57116 return this
58117 }
59118
60119 get content ( ) {
61- return this [ _manifest ]
120+ return this . #manifest
121+ }
122+
123+ get path ( ) {
124+ return this . #path
62125 }
63126
64127 update ( content ) {
65128 // validates both current manifest and content param
66129 const invalidContent =
67- typeof this [ _manifest ] !== 'object'
130+ typeof this . #manifest !== 'object'
68131 || typeof content !== 'object'
69132 if ( invalidContent ) {
70133 throw Object . assign (
@@ -74,36 +137,76 @@ class PackageJson {
74137 }
75138
76139 for ( const step of knownSteps ) {
77- this [ _manifest ] = step ( { content, originalContent : this [ _manifest ] } )
140+ this . #manifest = step ( { content, originalContent : this . #manifest } )
78141 }
79142
80143 // unknown properties will just be overwitten
81144 for ( const [ key , value ] of Object . entries ( content ) ) {
82145 if ( ! knownKeys . has ( key ) ) {
83- this [ _manifest ] [ key ] = value
146+ this . #manifest [ key ] = value
84147 }
85148 }
86149
87150 return this
88151 }
89152
90153 async save ( ) {
154+ if ( this . #fromIndex) {
155+ throw new Error ( 'No package.json to save to' )
156+ }
91157 const {
92158 [ Symbol . for ( 'indent' ) ] : indent ,
93159 [ Symbol . for ( 'newline' ) ] : newline ,
94- } = this [ _manifest ]
160+ } = this . #manifest
95161
96162 const format = indent === undefined ? ' ' : indent
97163 const eol = newline === undefined ? '\n' : newline
98164 const fileContent = `${
99- JSON . stringify ( this [ _manifest ] , null , format )
165+ JSON . stringify ( this . #manifest , null , format )
100166 } \n`
101167 . replace ( / \n / g, eol )
102168
103- if ( fileContent . trim ( ) !== this [ _readFileContent ] . trim ( ) ) {
104- return await writeFile ( this [ _filename ] , fileContent )
169+ if ( fileContent . trim ( ) !== this . #readFileContent . trim ( ) ) {
170+ return await writeFile ( this . #filename , fileContent )
105171 }
106172 }
173+
174+ async normalize ( opts = { } ) {
175+ if ( ! opts . steps ) {
176+ opts . steps = this . constructor . normalizeSteps
177+ }
178+ await this . load ( )
179+ await normalize ( this , opts )
180+ return this
181+ }
182+
183+ async prepare ( opts = { } ) {
184+ if ( ! opts . steps ) {
185+ opts . steps = this . constructor . prepareSteps
186+ }
187+ await this . load ( true )
188+ await normalize ( this , opts )
189+ return this
190+ }
191+ }
192+
193+ // /**package { "name": "foo", "version": "1.2.3", ... } **/
194+ function fromComment ( data ) {
195+ data = data . split ( / ^ \/ \* \* p a c k a g e (?: \s | $ ) / m)
196+
197+ if ( data . length < 2 ) {
198+ throw new Error ( 'File has no package in comments' )
199+ }
200+ data = data [ 1 ]
201+ data = data . split ( / \* \* \/ $ / m)
202+
203+ if ( data . length < 2 ) {
204+ throw new Error ( 'File has no package in comments' )
205+ }
206+ data = data [ 0 ]
207+ data = data . replace ( / ^ \s * \* / mg, '' )
208+
209+ return parseJSON ( data )
107210}
108211
109212module . exports = PackageJson
0 commit comments