@jsenv/package-publish 1.11.59 → 1.12.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/package-publish",
3
- "version": "1.11.59",
3
+ "version": "1.12.1",
4
4
  "type": "module",
5
5
  "description": "Publish package to one or many registry.",
6
6
  "repository": {
@@ -23,8 +23,8 @@
23
23
  "/src/"
24
24
  ],
25
25
  "dependencies": {
26
- "@jsenv/filesystem": "4.15.28",
27
- "@jsenv/humanize": "1.8.6",
26
+ "@jsenv/filesystem": "4.15.29",
27
+ "@jsenv/humanize": "1.8.7",
28
28
  "semver": "7.8.5"
29
29
  },
30
30
  "devDependencies": {
@@ -4,7 +4,6 @@ import { exec } from "node:child_process";
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { setNpmConfig } from "./set_npm_config.js";
7
- import { waitForStagedVersionToLand } from "./staged_version.js";
8
7
 
9
8
  export const publish = async ({
10
9
  logger,
@@ -112,6 +111,9 @@ export const publish = async ({
112
111
  reason: "already-published",
113
112
  });
114
113
  } else if (error.message.includes("previously staged version")) {
114
+ // an earlier publish of this version was accepted and the
115
+ // registry holds it (see version_in_registry.js): it exposes
116
+ // it later by itself, nothing more can be done from here
115
117
  resolve({
116
118
  success: true,
117
119
  reason: "staged",
@@ -149,29 +151,21 @@ export const publish = async ({
149
151
  });
150
152
  }
151
153
  });
152
- if (publishResult.reason === "already-published") {
153
- publishTask.setRightText(`(already published)`);
154
- } else if (publishResult.reason === "staged") {
155
- publishTask.setRightText(`(staged)`);
154
+ if (publishResult.reason === "staged") {
155
+ publishTask.happen(
156
+ `${packageSlug} is staged on ${registryUrl}, an earlier publish handed it over and the registry exposes it later`,
157
+ );
158
+ } else {
159
+ if (publishResult.reason === "already-published") {
160
+ publishTask.setRightText(`(already published)`);
161
+ }
162
+ publishTask.done();
156
163
  }
157
- publishTask.done();
158
164
  } finally {
159
165
  restoreProcessEnv();
160
166
  restorePackageFile();
161
167
  restoreNpmConfigFile();
162
168
  }
163
- if (publishResult.reason === "staged") {
164
- await waitForStagedVersionToLand({
165
- registryUrl,
166
- packageName: packageObject.name,
167
- packageVersion: packageObject.version,
168
- token,
169
- });
170
- return {
171
- success: true,
172
- reason: "already-published",
173
- };
174
- }
175
169
  return publishResult;
176
170
  } catch (e) {
177
171
  publishTask.fail();
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Whether the registry exposes a version, and waiting until it does.
3
+ *
4
+ * A version the registry accepted is not exposed right away: npm holds it
5
+ * ("staged") for minutes, sometimes hours. Nothing read from the registry tells
6
+ * a staged version apart from one that was never published, the packument
7
+ * ignores it and its tarball is a 404.
8
+ */
9
+
10
+ import { createTaskLog } from "@jsenv/humanize";
11
+
12
+ const POLL_INTERVAL_MS = 5_000;
13
+
14
+ export const waitForVersionInRegistry = async ({
15
+ registryUrl,
16
+ packageName,
17
+ packageVersion,
18
+ token,
19
+ timeout,
20
+ timeoutErrorMessage,
21
+ }) => {
22
+ const waitTask = createTaskLog(
23
+ `wait for ${packageName}@${packageVersion} to be published by ${registryUrl}`,
24
+ );
25
+ const msBeforeTimeout = Date.now() + timeout;
26
+ try {
27
+ while (true) {
28
+ const versionIsInRegistry = await checkVersionIsInRegistry({
29
+ registryUrl,
30
+ packageName,
31
+ packageVersion,
32
+ token,
33
+ });
34
+ if (versionIsInRegistry) {
35
+ waitTask.done();
36
+ return;
37
+ }
38
+ if (Date.now() > msBeforeTimeout) {
39
+ throw new Error(timeoutErrorMessage);
40
+ }
41
+ await new Promise((resolve) => {
42
+ setTimeout(resolve, POLL_INTERVAL_MS);
43
+ });
44
+ }
45
+ } catch (e) {
46
+ waitTask.fail();
47
+ throw e;
48
+ }
49
+ };
50
+
51
+ export const checkVersionIsInRegistry = async ({
52
+ registryUrl,
53
+ packageName,
54
+ packageVersion,
55
+ token,
56
+ }) => {
57
+ let response;
58
+ try {
59
+ response = await fetch(`${registryUrl}/${packageName}`, {
60
+ headers: {
61
+ "accept":
62
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
63
+ // the registry is served by a cache; without this a version can stay
64
+ // invisible long after it landed
65
+ "cache-control": "no-cache",
66
+ ...(token ? { authorization: `token ${token}` } : {}),
67
+ },
68
+ });
69
+ } catch {
70
+ // a network hiccup is one more reason for the version not to be there yet
71
+ return false;
72
+ }
73
+ if (response.status !== 200) {
74
+ return false;
75
+ }
76
+ const packageObject = await response.json();
77
+ return Boolean(packageObject.versions[packageVersion]);
78
+ };
@@ -1,158 +0,0 @@
1
- /*
2
- * A version lands on the registry in two steps: the registry takes the tarball
3
- * ("staged"), then it exposes the version. While a version is staged the
4
- * registry answers 409 'Cannot publish over previously staged version "x.y.z"'
5
- * to any publish of it, so waiting for it to be exposed is the only way through.
6
- *
7
- * The packument says nothing about a staged version, but its tarball is already
8
- * served: that is what tells a staged version apart from one that was never
9
- * published.
10
- */
11
-
12
- import { createTaskLog } from "@jsenv/humanize";
13
-
14
- export const VERSION_STATUS = {
15
- ABSENT: "absent",
16
- STAGED: "staged",
17
- PUBLISHED: "published",
18
- };
19
-
20
- const STAGED_VERSION_TIMEOUT_MS = 300_000;
21
- const STAGED_VERSION_POLL_INTERVAL_MS = 5_000;
22
-
23
- export const checkVersionStatusInRegistry = async ({
24
- registryUrl,
25
- packageName,
26
- packageVersion,
27
- token,
28
- }) => {
29
- const versionIsInRegistry = await checkVersionIsInRegistry({
30
- registryUrl,
31
- packageName,
32
- packageVersion,
33
- token,
34
- });
35
- if (versionIsInRegistry) {
36
- return VERSION_STATUS.PUBLISHED;
37
- }
38
- const tarballIsInRegistry = await checkTarballIsInRegistry({
39
- registryUrl,
40
- packageName,
41
- packageVersion,
42
- token,
43
- });
44
- if (tarballIsInRegistry) {
45
- return VERSION_STATUS.STAGED;
46
- }
47
- return VERSION_STATUS.ABSENT;
48
- };
49
-
50
- export const waitForStagedVersionToLand = ({
51
- registryUrl,
52
- packageName,
53
- packageVersion,
54
- token,
55
- }) => {
56
- return waitForVersionInRegistry({
57
- registryUrl,
58
- packageName,
59
- packageVersion,
60
- token,
61
- timeout: STAGED_VERSION_TIMEOUT_MS,
62
- timeoutErrorMessage: `${packageName}@${packageVersion} is staged on ${registryUrl} but did not get published. Run the publish again to keep waiting; a staged version cannot be published again.`,
63
- });
64
- };
65
-
66
- export const waitForVersionInRegistry = async ({
67
- registryUrl,
68
- packageName,
69
- packageVersion,
70
- token,
71
- timeout,
72
- timeoutErrorMessage,
73
- }) => {
74
- const waitTask = createTaskLog(
75
- `wait for ${packageName}@${packageVersion} to be published by ${registryUrl}`,
76
- );
77
- const msBeforeTimeout = Date.now() + timeout;
78
- try {
79
- while (true) {
80
- const versionIsInRegistry = await checkVersionIsInRegistry({
81
- registryUrl,
82
- packageName,
83
- packageVersion,
84
- token,
85
- });
86
- if (versionIsInRegistry) {
87
- waitTask.done();
88
- return;
89
- }
90
- if (Date.now() > msBeforeTimeout) {
91
- throw new Error(timeoutErrorMessage);
92
- }
93
- await new Promise((resolve) => {
94
- setTimeout(resolve, STAGED_VERSION_POLL_INTERVAL_MS);
95
- });
96
- }
97
- } catch (e) {
98
- waitTask.fail();
99
- throw e;
100
- }
101
- };
102
-
103
- const checkVersionIsInRegistry = async ({
104
- registryUrl,
105
- packageName,
106
- packageVersion,
107
- token,
108
- }) => {
109
- const response = await fetchRegistry(`${registryUrl}/${packageName}`, {
110
- method: "GET",
111
- token,
112
- });
113
- if (!response || response.status !== 200) {
114
- return false;
115
- }
116
- const packageObject = await response.json();
117
- return Boolean(packageObject.versions[packageVersion]);
118
- };
119
-
120
- const checkTarballIsInRegistry = async ({
121
- registryUrl,
122
- packageName,
123
- packageVersion,
124
- token,
125
- }) => {
126
- const response = await fetchRegistry(
127
- computeTarballUrl({ registryUrl, packageName, packageVersion }),
128
- { method: "HEAD", token },
129
- );
130
- return Boolean(response) && response.status === 200;
131
- };
132
-
133
- const computeTarballUrl = ({ registryUrl, packageName, packageVersion }) => {
134
- const packageBasename =
135
- packageName[0] === "@"
136
- ? packageName.slice(packageName.indexOf("/") + 1)
137
- : packageName;
138
- return `${registryUrl}/${packageName}/-/${packageBasename}-${packageVersion}.tgz`;
139
- };
140
-
141
- const fetchRegistry = async (url, { method, token }) => {
142
- try {
143
- return await fetch(url, {
144
- method,
145
- headers: {
146
- "accept":
147
- "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
148
- // the registry is served by a cache; without this a version can stay
149
- // invisible long after it landed
150
- "cache-control": "no-cache",
151
- ...(token ? { authorization: `token ${token}` } : {}),
152
- },
153
- });
154
- } catch {
155
- // a network hiccup is one more reason for the version not to be there yet
156
- return null;
157
- }
158
- };