@jsenv/package-publish 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,510 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var logger = require('@jsenv/logger');
6
- var cancellation = require('@jsenv/cancellation');
7
- var util = require('@jsenv/util');
8
- var server = require('@jsenv/server');
9
- var child_process = require('child_process');
10
- var module$1 = require('module');
11
-
12
- // https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#getpackageversion
13
- const fetchLatestInRegistry = async ({
14
- registryUrl,
15
- packageName,
16
- token
17
- }) => {
18
- const requestUrl = `${registryUrl}/${packageName}`;
19
- const response = await server.fetchUrl(requestUrl, {
20
- method: "GET",
21
- headers: {
22
- // "user-agent": "jsenv",
23
- accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
24
- ...(token ? {
25
- authorization: `token ${token}`
26
- } : {})
27
- }
28
- });
29
- const responseStatus = response.status;
30
-
31
- if (responseStatus === 404) {
32
- return null;
33
- }
34
-
35
- if (responseStatus !== 200) {
36
- throw new Error(writeUnexpectedResponseStatus({
37
- requestUrl,
38
- responseStatus,
39
- responseText: await response.text()
40
- }));
41
- }
42
-
43
- const packageObject = await response.json();
44
- return packageObject.versions[packageObject["dist-tags"].latest];
45
- };
46
-
47
- const writeUnexpectedResponseStatus = ({
48
- requestUrl,
49
- responseStatus,
50
- responseText
51
- }) => `package registry response status should be 200.
52
- --- request url ----
53
- ${requestUrl}
54
- --- response status ---
55
- ${responseStatus}
56
- --- response text ---
57
- ${responseText}`;
58
-
59
- const setNpmConfig = (configString, configObject) => {
60
- return Object.keys(configObject).reduce((previous, key) => {
61
- return setOrUpdateNpmConfig(previous, key, configObject[key]);
62
- }, configString);
63
- };
64
-
65
- const setOrUpdateNpmConfig = (config, key, value) => {
66
- const assignmentIndex = config.indexOf(`${key}=`);
67
-
68
- if (assignmentIndex === -1) {
69
- if (config === "") {
70
- return `${key}=${value}`;
71
- }
72
-
73
- return `${config}
74
- ${key}=${value}`;
75
- }
76
-
77
- const beforeAssignment = config.slice(0, assignmentIndex);
78
- const nextLineIndex = config.indexOf("\n", assignmentIndex);
79
-
80
- if (nextLineIndex === -1) {
81
- return `${beforeAssignment}${key}=${value}`;
82
- }
83
-
84
- const afterAssignment = config.slice(nextLineIndex);
85
- return `${beforeAssignment}${key}=${value}${afterAssignment}`;
86
- };
87
-
88
- const publish = async ({
89
- logger,
90
- logNpmPublishOutput,
91
- projectDirectoryUrl,
92
- registryUrl,
93
- token
94
- }) => {
95
- try {
96
- const promises = [];
97
- const previousValue = process.env.NODE_AUTH_TOKEN;
98
-
99
- const restoreProcessEnv = () => {
100
- process.env.NODE_AUTH_TOKEN = previousValue;
101
- };
102
-
103
- process.env.NODE_AUTH_TOKEN = token;
104
- const projectPackageFileUrl = util.resolveUrl("./package.json", projectDirectoryUrl);
105
- const projectPackageString = await util.readFile(projectPackageFileUrl);
106
-
107
- const restoreProjectPackageFile = () => util.writeFile(projectPackageFileUrl, projectPackageString);
108
-
109
- const projectPackageObject = JSON.parse(projectPackageString);
110
- projectPackageObject.publishConfig = projectPackageObject.publishConfig || {};
111
- projectPackageObject.publishConfig.registry = registryUrl;
112
- promises.push(util.writeFile(projectPackageFileUrl, JSON.stringify(projectPackageObject, null, " ")));
113
- const projectNpmConfigFileUrl = util.resolveUrl("./.npmrc", projectDirectoryUrl);
114
- let projectNpmConfigString;
115
-
116
- try {
117
- projectNpmConfigString = await util.readFile(projectNpmConfigFileUrl);
118
- } catch (e) {
119
- if (e.code === "ENOENT") {
120
- projectNpmConfigString = "";
121
- } else {
122
- throw e;
123
- }
124
- }
125
-
126
- const restoreProjectNpmConfigFile = () => util.writeFile(projectNpmConfigFileUrl, projectNpmConfigString);
127
-
128
- promises.push(util.writeFile(projectNpmConfigFileUrl, setNpmConfig(projectNpmConfigString, {
129
- [computeRegistryTokenKey(registryUrl)]: token,
130
- [computeRegistryKey(projectPackageObject.name)]: registryUrl
131
- })));
132
- await Promise.all(promises);
133
-
134
- try {
135
- return await new Promise((resolve, reject) => {
136
- const command = child_process.exec("npm publish", {
137
- cwd: util.urlToFileSystemPath(projectDirectoryUrl),
138
- stdio: "silent"
139
- }, error => {
140
- if (error) {
141
- // publish conflict generally occurs because servers
142
- // returns 200 after npm publish
143
- // but returns previous version if asked immediatly
144
- // after for the last published version.
145
- // TODO: ideally we should catch 404 error returned from npm
146
- // it happens it the token is not allowed to publish
147
- // a repository. And when we detect this we display a more useful message
148
- // suggesting the token rights are insufficient to publish the package
149
- // npm publish conclit
150
- if (error.message.includes("EPUBLISHCONFLICT")) {
151
- resolve({
152
- success: true,
153
- reason: "already-published"
154
- });
155
- } else if (error.message.includes("Cannot publish over existing version")) {
156
- resolve({
157
- success: true,
158
- reason: "already-published"
159
- });
160
- } else if (error.message.includes("You cannot publish over the previously published versions")) {
161
- resolve({
162
- success: true,
163
- reason: "already-published"
164
- });
165
- } // github publish conflict
166
- else if (error.message.includes("ambiguous package version in package.json")) {
167
- resolve({
168
- success: true,
169
- reason: "already-published"
170
- });
171
- } else {
172
- reject(error);
173
- }
174
- } else {
175
- resolve({
176
- success: true,
177
- reason: "published"
178
- });
179
- }
180
- });
181
-
182
- if (logNpmPublishOutput) {
183
- command.stdout.on("data", data => {
184
- logger.debug(data);
185
- });
186
- command.stderr.on("data", data => {
187
- // debug because this output is part of
188
- // the error message generated by a failing npm publish
189
- logger.debug(data);
190
- });
191
- }
192
- });
193
- } finally {
194
- await Promise.all([restoreProcessEnv(), restoreProjectPackageFile(), restoreProjectNpmConfigFile()]);
195
- }
196
- } catch (e) {
197
- return {
198
- success: false,
199
- reason: e
200
- };
201
- }
202
- };
203
-
204
- const computeRegistryTokenKey = registryUrl => {
205
- if (registryUrl.startsWith("http://")) {
206
- return `${registryUrl.slice("http:".length)}/:_authToken`;
207
- }
208
-
209
- if (registryUrl.startsWith("https://")) {
210
- return `${registryUrl.slice("https:".length)}/:_authToken`;
211
- }
212
-
213
- if (registryUrl.startsWith("//")) {
214
- return `${registryUrl}/:_authToken`;
215
- }
216
-
217
- throw new Error(`registryUrl must start with http or https, got ${registryUrl}`);
218
- };
219
-
220
- const computeRegistryKey = packageName => {
221
- if (packageName[0] === "@") {
222
- const packageScope = packageName.slice(0, packageName.indexOf("/"));
223
- return `${packageScope}:registry`;
224
- }
225
-
226
- return `registry`;
227
- };
228
-
229
- const readProjectPackage = async ({
230
- projectDirectoryUrl
231
- }) => {
232
- const packageFileUrl = util.resolveUrl("./package.json", projectDirectoryUrl);
233
- let packageObject;
234
-
235
- try {
236
- const packageString = await util.readFile(packageFileUrl);
237
-
238
- try {
239
- packageObject = JSON.parse(packageString);
240
- } catch (e) {
241
- if (e.name === "SyntaxError") {
242
- throw new Error(`syntax error while parsing project package.json
243
- --- syntax error stack ---
244
- ${e.stack}
245
- --- package.json path ---
246
- ${util.urlToFileSystemPath(packageFileUrl)}`);
247
- }
248
-
249
- throw e;
250
- }
251
- } catch (e) {
252
- if (e.code === "ENOENT") {
253
- throw new Error(`cannot find project package.json
254
- --- package.json path ---
255
- ${util.urlToFileSystemPath(packageFileUrl)}`);
256
- }
257
-
258
- throw e;
259
- }
260
-
261
- return packageObject;
262
- };
263
-
264
- /* global __filename */
265
- const filenameContainsBackSlashes = __filename.indexOf("\\") > -1;
266
- const url = filenameContainsBackSlashes ? `file:///${__filename.replace(/\\/g, "/")}` : `file://${__filename}`;
267
-
268
- const require$1 = module$1.createRequire(url); // https://github.com/npm/node-semver#readme
269
-
270
-
271
- const {
272
- gt: versionGreaterThan,
273
- prerelease: versionToPrerelease
274
- } = require$1("semver");
275
-
276
- const PUBLISH_BECAUSE_NEVER_PUBLISHED = "never-published";
277
- const PUBLISH_BECAUSE_LATEST_LOWER = "latest-lower";
278
- const PUBLISH_BECAUSE_TAG_DIFFERS = "tag-differs";
279
- const NOTHING_BECAUSE_LATEST_HIGHER = "latest-higher";
280
- const NOTHING_BECAUSE_ALREADY_PUBLISHED = "already-published";
281
- const needsPublish = ({
282
- registryLatestVersion,
283
- packageVersion
284
- }) => {
285
- if (registryLatestVersion === null) {
286
- return PUBLISH_BECAUSE_NEVER_PUBLISHED;
287
- }
288
-
289
- if (registryLatestVersion === packageVersion) {
290
- return NOTHING_BECAUSE_ALREADY_PUBLISHED;
291
- }
292
-
293
- if (versionGreaterThan(registryLatestVersion, packageVersion)) {
294
- return NOTHING_BECAUSE_LATEST_HIGHER;
295
- }
296
-
297
- const registryLatestVersionPrerelease = versionToPrerelease(registryLatestVersion);
298
- const packageVersionPrerelease = versionToPrerelease(packageVersion);
299
-
300
- if (registryLatestVersionPrerelease === null && packageVersionPrerelease === null) {
301
- return PUBLISH_BECAUSE_LATEST_LOWER;
302
- }
303
-
304
- if (registryLatestVersionPrerelease === null && packageVersionPrerelease !== null) {
305
- return PUBLISH_BECAUSE_LATEST_LOWER;
306
- }
307
-
308
- if (registryLatestVersionPrerelease !== null && packageVersionPrerelease === null) {
309
- return PUBLISH_BECAUSE_LATEST_LOWER;
310
- }
311
-
312
- const [registryReleaseTag, registryPrereleaseVersion] = registryLatestVersionPrerelease;
313
- const [packageReleaseTag, packagePreReleaseVersion] = packageVersionPrerelease;
314
-
315
- if (registryReleaseTag !== packageReleaseTag) {
316
- return PUBLISH_BECAUSE_TAG_DIFFERS;
317
- }
318
-
319
- if (registryPrereleaseVersion === packagePreReleaseVersion) {
320
- return NOTHING_BECAUSE_ALREADY_PUBLISHED;
321
- }
322
-
323
- if (registryPrereleaseVersion > packagePreReleaseVersion) {
324
- return NOTHING_BECAUSE_LATEST_HIGHER;
325
- }
326
-
327
- return PUBLISH_BECAUSE_LATEST_LOWER;
328
- };
329
-
330
- const publishPackage = async ({
331
- cancellationToken = cancellation.createCancellationTokenForProcess(),
332
- logLevel,
333
- projectDirectoryUrl,
334
- registriesConfig,
335
- logNpmPublishOutput = true,
336
- updateProcessExitCode = true
337
- } = {}) => {
338
- return cancellation.executeAsyncFunction(async () => {
339
- const logger$1 = logger.createLogger({
340
- logLevel
341
- });
342
- logger$1.debug(`publishPackage(${JSON.stringify({
343
- projectDirectoryUrl,
344
- logLevel,
345
- registriesConfig
346
- }, null, " ")})`);
347
- projectDirectoryUrl = util.assertAndNormalizeDirectoryUrl(projectDirectoryUrl);
348
- assertRegistriesConfig(registriesConfig);
349
- logger$1.debug(`reading project package.json`);
350
- const packageInProject = await readProjectPackage({
351
- projectDirectoryUrl
352
- });
353
- const {
354
- name: packageName,
355
- version: packageVersion
356
- } = packageInProject;
357
- logger$1.info(`${packageName}@${packageVersion} found in package.json`);
358
- const report = {};
359
- await Promise.all(Object.keys(registriesConfig).map(async registryUrl => {
360
- const registryReport = {
361
- packageName,
362
- packageVersion,
363
- registryLatestVersion: undefined,
364
- action: undefined,
365
- actionReason: undefined,
366
- actionResult: undefined
367
- };
368
- report[registryUrl] = registryReport;
369
-
370
- if (packageInProject.private) {
371
- registryReport.action = "nothing";
372
- registryReport.actionReason = "package is private";
373
- return;
374
- }
375
-
376
- logger$1.debug(`check latest version for ${packageName} in ${registryUrl}`);
377
- const registryConfig = registriesConfig[registryUrl];
378
-
379
- try {
380
- const latestPackageInRegistry = await fetchLatestInRegistry({
381
- registryUrl,
382
- packageName,
383
- ...registryConfig
384
- });
385
- const registryLatestVersion = latestPackageInRegistry === null ? null : latestPackageInRegistry.version;
386
- registryReport.registryLatestVersion = registryLatestVersion;
387
- const needs = needsPublish({
388
- packageVersion,
389
- registryLatestVersion
390
- });
391
- registryReport.action = needs === PUBLISH_BECAUSE_NEVER_PUBLISHED || needs === PUBLISH_BECAUSE_LATEST_LOWER || needs === PUBLISH_BECAUSE_TAG_DIFFERS ? "publish" : "nothing";
392
- registryReport.actionReason = needs;
393
- } catch (e) {
394
- registryReport.action = "nothing";
395
- registryReport.actionReason = e;
396
-
397
- if (updateProcessExitCode) {
398
- process.exitCode = 1;
399
- }
400
- }
401
-
402
- cancellationToken.throwIfRequested();
403
- })); // we have to publish in serie because we don't fully control
404
- // npm publish, we have to enforce where the package gets published
405
-
406
- await Object.keys(report).reduce(async (previous, registryUrl) => {
407
- await previous;
408
- cancellationToken.throwIfRequested();
409
- const registryReport = report[registryUrl];
410
- const {
411
- action,
412
- actionReason,
413
- registryLatestVersion
414
- } = registryReport;
415
-
416
- if (action === "nothing") {
417
- if (actionReason === NOTHING_BECAUSE_ALREADY_PUBLISHED) {
418
- logger$1.info(`skip ${packageName}@${packageVersion} publish on ${registryUrl} because already published`);
419
- } else if (actionReason === NOTHING_BECAUSE_LATEST_HIGHER) {
420
- logger$1.info(`skip ${packageName}@${packageVersion} publish on ${registryUrl} because latest version is higher (${registryLatestVersion})`);
421
- } else if (actionReason === "package is private") {
422
- logger$1.info(`skip ${packageName}@${packageVersion} publish on ${registryUrl} because found private: true in package.json`);
423
- } else {
424
- logger$1.error(`skip ${packageName}@${packageVersion} publish on ${registryUrl} due to error while fetching latest version.
425
- --- error stack ---
426
- ${actionReason.stack}`);
427
- }
428
-
429
- registryReport.actionResult = {
430
- success: true,
431
- reason: "nothing-to-do"
432
- };
433
- return;
434
- }
435
-
436
- if (actionReason === PUBLISH_BECAUSE_NEVER_PUBLISHED) {
437
- logger$1.info(`publish ${packageName}@${packageVersion} on ${registryUrl} because it was never published`);
438
- } else if (actionReason === PUBLISH_BECAUSE_LATEST_LOWER) {
439
- logger$1.info(`publish ${packageName}@${packageVersion} on ${registryUrl} because latest version is lower (${registryLatestVersion})`);
440
- } else if (actionReason === PUBLISH_BECAUSE_TAG_DIFFERS) {
441
- logger$1.info(`publish ${packageName}@${packageVersion} on ${registryUrl} because latest tag differs (${registryLatestVersion})`);
442
- }
443
-
444
- const {
445
- success,
446
- reason
447
- } = await publish({
448
- logger: logger$1,
449
- logNpmPublishOutput,
450
- projectDirectoryUrl,
451
- registryUrl,
452
- ...registriesConfig[registryUrl]
453
- });
454
- registryReport.actionResult = {
455
- success,
456
- reason
457
- };
458
-
459
- if (success) {
460
- if (reason === "already-published") {
461
- logger$1.info(`${packageName}@${packageVersion} was already published on ${registryUrl}`);
462
- } else {
463
- logger$1.info(`${packageName}@${packageVersion} published on ${registryUrl}`);
464
- }
465
- } else {
466
- logger$1.error(`error when publishing ${packageName}@${packageVersion} in ${registryUrl}
467
- --- error stack ---
468
- ${reason.stack}`);
469
-
470
- if (updateProcessExitCode) {
471
- process.exitCode = 1;
472
- }
473
- }
474
- }, Promise.resolve());
475
- return report;
476
- }, {
477
- catchCancellation: true,
478
- considerUnhandledRejectionsAsExceptions: true
479
- });
480
- };
481
-
482
- const assertRegistriesConfig = value => {
483
- if (typeof value !== "object" || value === null) {
484
- throw new TypeError(`registriesConfig must be an object, got ${value}`);
485
- }
486
-
487
- Object.keys(value).forEach(registryUrl => {
488
- const registryMapValue = value[registryUrl];
489
-
490
- if (typeof registryMapValue !== "object" || value === null) {
491
- throw new TypeError(`Unexpected value in registriesConfig for ${registryUrl}. It must be an object, got ${registryMapValue}`);
492
- }
493
-
494
- if (`token` in registryMapValue === false || registryMapValue.token === "") {
495
- throw new TypeError(`Missing token in registriesConfig for ${registryUrl}.`);
496
- }
497
-
498
- const {
499
- token
500
- } = registryMapValue;
501
-
502
- if (typeof token !== "string") {
503
- throw new TypeError(`Unexpected token in registriesConfig for ${registryUrl}. It must be a string, got ${token}.`);
504
- }
505
- });
506
- };
507
-
508
- exports.publishPackage = publishPackage;
509
-
510
- //# sourceMappingURL=jsenv_package_publish.cjs.map