@@ -4,6 +4,7 @@ const path = require("path");
4
4
const spawn = require ( "cross-spawn" ) ;
5
5
const readPkg = require ( "read-pkg" ) ;
6
6
const validatePkgName = require ( "validate-npm-package-name" ) ;
7
+ const { extractJSONObject } = require ( "extract-first-json" ) ;
7
8
/**
8
9
* @param {string } [filePathOrDirPath]
9
10
* @returns {Promise<readPkg.NormalizedPackageJson> }
@@ -63,43 +64,122 @@ const checkPrivateField = (packagePath) => {
63
64
* or rejects if anything failed
64
65
* @param packageName
65
66
* @param registry
67
+ * @param {{verbose : boolean} } options
66
68
* @returns {Promise }
67
69
*/
68
- const viewPackage = ( packageName , registry ) => {
70
+ const viewPackage = ( packageName , registry , options ) => {
69
71
return new Promise ( ( resolve , reject ) => {
70
72
const registryArgs = registry ? [ "--registry" , registry ] : [ ] ;
71
73
const view = spawn ( "npm" , [ "view" , packageName , "versions" , "--json" ] . concat ( registryArgs ) ) ;
72
- let result = "" ;
73
- let errorResult = "" ;
74
+ let _stdoutResult = "" ;
75
+ let _stderrResult = "" ;
74
76
77
+ /**
78
+ * @param stdout
79
+ * @param stderr
80
+ * @returns {{stdoutJSON: null | {}, stderrJSON: null | {}} }
81
+ */
82
+ const getJsonOutputs = ( { stdout, stderr } ) => {
83
+ let stdoutJSON = null ;
84
+ let stderrJSON = null ;
85
+ if ( stdout ) {
86
+ try {
87
+ stdoutJSON = JSON . parse ( stdout ) ;
88
+ } catch ( error ) {
89
+ // nope
90
+ if ( options . verbose ) {
91
+ console . error ( "stdoutJSON parse error" , stdout ) ;
92
+ }
93
+ }
94
+ }
95
+ if ( stderr ) {
96
+ try {
97
+ stderrJSON = JSON . parse ( stderr ) ;
98
+ } catch ( error ) {
99
+ // nope
100
+ if ( options . verbose ) {
101
+ console . error ( "stderrJSON parse error" , stdout ) ;
102
+ }
103
+ }
104
+ }
105
+ return {
106
+ stdoutJSON,
107
+ stderrJSON
108
+ } ;
109
+ } ;
110
+ const isError = ( json ) => {
111
+ return json && "error" in json ;
112
+ } ;
113
+ const is404Error = ( json ) => {
114
+ return isError ( json ) && json . error . code === "E404" ;
115
+ } ;
75
116
view . stdout . on ( "data" , ( data ) => {
76
- result += data . toString ( ) ;
117
+ _stdoutResult += data . toString ( ) ;
77
118
} ) ;
78
119
79
120
view . stderr . on ( "data" , ( err ) => {
80
- errorResult += err . toString ( ) ;
121
+ const stdErrorStr = err . toString ( ) ;
122
+ // Workaround for npm 7
123
+ // npm 7 --json option is broken
124
+ // It aim to remove non json output.
125
+ // FIXME: However,This logics will break json chunk(chunk may be invalid json)
126
+ // https://github.com/azu/can-npm-publish/issues/19
127
+ // https://github.com/npm/cli/issues/2740
128
+ const jsonObject = extractJSONObject ( stdErrorStr ) ;
129
+ if ( jsonObject ) {
130
+ _stderrResult = JSON . stringify ( jsonObject , null , 4 ) ;
131
+ }
81
132
} ) ;
82
133
83
134
view . on ( "close" , ( code ) => {
135
+ // Note:
136
+ // npm 6 output JSON in stdout
137
+ // npm 7(7.18.1) output JSON in stderr
138
+ const { stdoutJSON, stderrJSON } = getJsonOutputs ( {
139
+ stdout : _stdoutResult ,
140
+ stderr : _stderrResult
141
+ } ) ;
142
+ if ( options . verbose ) {
143
+ console . log ( "`npm view` command's exit code:" , code ) ;
144
+ console . log ( "`npm view` stdoutJSON" , stdoutJSON ) ;
145
+ console . log ( "`npm view` stderrJSON" , stderrJSON ) ;
146
+ }
147
+ // npm6 view --json output to stdout if the package is 404 → can publish
148
+ if ( is404Error ( stdoutJSON ) ) {
149
+ return resolve ( [ ] ) ;
150
+ }
151
+ // npm7 view --json output to stderr if the package is 404 → can publish
152
+ if ( is404Error ( stderrJSON ) ) {
153
+ return resolve ( [ ] ) ;
154
+ }
155
+ // in other error, can not publish → reject
156
+ if ( isError ( stdoutJSON ) ) {
157
+ return reject ( new Error ( _stdoutResult ) ) ;
158
+ }
159
+ if ( isError ( stderrJSON ) ) {
160
+ return reject ( new Error ( _stderrResult ) ) ;
161
+ }
162
+ // if command is failed by other reasons(no json output), treat it as actual error
84
163
if ( code !== 0 ) {
85
- return reject ( new Error ( errorResult ) ) ;
164
+ return reject ( new Error ( _stderrResult ) ) ;
86
165
}
87
- const resultJSON = JSON . parse ( result ) ;
88
- if ( resultJSON && resultJSON . error ) {
89
- // the package is not in the npm registry => can publish
90
- if ( resultJSON . error . code === "E404" ) {
91
- return resolve ( [ ] ) ; // resolve as empty version
92
- } else {
93
- // other error => can not publish
94
- return reject ( new Error ( errorResult ) ) ;
95
- }
166
+ if ( stdoutJSON ) {
167
+ // if success to get, resolve with versions json
168
+ return resolve ( stdoutJSON ) ;
169
+ } else {
170
+ return reject ( _stderrResult ) ;
96
171
}
97
- resolve ( resultJSON ) ;
98
172
} ) ;
99
173
} ) ;
100
174
} ;
101
175
102
- const checkAlreadyPublish = ( packagePath ) => {
176
+ /**
177
+ *
178
+ * @param {string } packagePath
179
+ * @param {{verbose : boolean} } options
180
+ * @returns {Promise<readPkg.NormalizedPackageJson> }
181
+ */
182
+ const checkAlreadyPublish = ( packagePath , options ) => {
103
183
return readPkgWithPath ( packagePath ) . then ( ( pkg ) => {
104
184
const name = pkg [ "name" ] ;
105
185
const version = pkg [ "version" ] ;
@@ -111,7 +191,7 @@ const checkAlreadyPublish = (packagePath) => {
111
191
if ( version === undefined ) {
112
192
return Promise . reject ( new Error ( "This package has no `version`." ) ) ;
113
193
}
114
- return viewPackage ( name , registry ) . then ( ( versions ) => {
194
+ return viewPackage ( name , registry , options ) . then ( ( versions ) => {
115
195
if ( versions . includes ( version ) ) {
116
196
return Promise . reject ( new Error ( `${ name } @${ version } is already published` ) ) ;
117
197
}
@@ -128,7 +208,7 @@ const checkAlreadyPublish = (packagePath) => {
128
208
const canNpmPublish = ( packagePath , options = { verbose : false } ) => {
129
209
return Promise . all ( [
130
210
checkPkgName ( packagePath , options ) ,
131
- checkAlreadyPublish ( packagePath ) ,
211
+ checkAlreadyPublish ( packagePath , options ) ,
132
212
checkPrivateField ( packagePath )
133
213
] ) ;
134
214
} ;
0 commit comments