@jsenv/package-publish 1.11.58 → 1.12.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/package-publish",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
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.
|
|
27
|
-
"@jsenv/humanize": "1.8.
|
|
26
|
+
"@jsenv/filesystem": "4.15.28",
|
|
27
|
+
"@jsenv/humanize": "1.8.6",
|
|
28
28
|
"semver": "7.8.5"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
package/src/internal/publish.js
CHANGED
|
@@ -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,
|
|
@@ -16,12 +15,16 @@ export const publish = async ({
|
|
|
16
15
|
}) => {
|
|
17
16
|
const publishTask = createTaskLog(`publish ${packageSlug} on ${registryUrl}`);
|
|
18
17
|
try {
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
// without a token npm authenticates by itself, for instance through
|
|
19
|
+
// trusted publishing (OIDC) in a GitHub workflow
|
|
20
|
+
let restoreProcessEnv = () => {};
|
|
21
|
+
if (token) {
|
|
22
|
+
const previousValue = process.env.NODE_AUTH_TOKEN;
|
|
23
|
+
restoreProcessEnv = () => {
|
|
24
|
+
process.env.NODE_AUTH_TOKEN = previousValue;
|
|
25
|
+
};
|
|
26
|
+
process.env.NODE_AUTH_TOKEN = token;
|
|
27
|
+
}
|
|
25
28
|
// updating package.json to publish on the correct registry
|
|
26
29
|
let restorePackageFile = () => {};
|
|
27
30
|
const rootPackageFileUrl = new URL("./package.json", rootDirectoryUrl);
|
|
@@ -41,7 +44,7 @@ export const publish = async ({
|
|
|
41
44
|
JSON.stringify(packageObject, null, " "),
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
|
-
// updating .npmrc to add the token
|
|
47
|
+
// updating .npmrc to add the registry (and the token when there is one)
|
|
45
48
|
const npmConfigFileUrl = new URL("./.npmrc", rootDirectoryUrl);
|
|
46
49
|
let restoreNpmConfigFile;
|
|
47
50
|
let npmConfigFileContent;
|
|
@@ -60,7 +63,7 @@ export const publish = async ({
|
|
|
60
63
|
writeFileSync(
|
|
61
64
|
npmConfigFileUrl,
|
|
62
65
|
setNpmConfig(npmConfigFileContent, {
|
|
63
|
-
[computeRegistryTokenKey(registryUrl)]: token,
|
|
66
|
+
...(token ? { [computeRegistryTokenKey(registryUrl)]: token } : {}),
|
|
64
67
|
[computeRegistryKey(packageObject.name)]: registryUrl,
|
|
65
68
|
}),
|
|
66
69
|
);
|
|
@@ -108,6 +111,9 @@ export const publish = async ({
|
|
|
108
111
|
reason: "already-published",
|
|
109
112
|
});
|
|
110
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
|
|
111
117
|
resolve({
|
|
112
118
|
success: true,
|
|
113
119
|
reason: "staged",
|
|
@@ -145,29 +151,21 @@ export const publish = async ({
|
|
|
145
151
|
});
|
|
146
152
|
}
|
|
147
153
|
});
|
|
148
|
-
if (publishResult.reason === "
|
|
149
|
-
publishTask.
|
|
150
|
-
|
|
151
|
-
|
|
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();
|
|
152
163
|
}
|
|
153
|
-
publishTask.done();
|
|
154
164
|
} finally {
|
|
155
165
|
restoreProcessEnv();
|
|
156
166
|
restorePackageFile();
|
|
157
167
|
restoreNpmConfigFile();
|
|
158
168
|
}
|
|
159
|
-
if (publishResult.reason === "staged") {
|
|
160
|
-
await waitForStagedVersionToLand({
|
|
161
|
-
registryUrl,
|
|
162
|
-
packageName: packageObject.name,
|
|
163
|
-
packageVersion: packageObject.version,
|
|
164
|
-
token,
|
|
165
|
-
});
|
|
166
|
-
return {
|
|
167
|
-
success: true,
|
|
168
|
-
reason: "already-published",
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
169
|
return publishResult;
|
|
172
170
|
} catch (e) {
|
|
173
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,142 +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 = async ({
|
|
51
|
-
registryUrl,
|
|
52
|
-
packageName,
|
|
53
|
-
packageVersion,
|
|
54
|
-
token,
|
|
55
|
-
}) => {
|
|
56
|
-
const waitTask = createTaskLog(
|
|
57
|
-
`wait for ${packageName}@${packageVersion} to be published by ${registryUrl}`,
|
|
58
|
-
);
|
|
59
|
-
const msBeforeTimeout = Date.now() + STAGED_VERSION_TIMEOUT_MS;
|
|
60
|
-
try {
|
|
61
|
-
while (true) {
|
|
62
|
-
const versionIsInRegistry = await checkVersionIsInRegistry({
|
|
63
|
-
registryUrl,
|
|
64
|
-
packageName,
|
|
65
|
-
packageVersion,
|
|
66
|
-
token,
|
|
67
|
-
});
|
|
68
|
-
if (versionIsInRegistry) {
|
|
69
|
-
waitTask.done();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (Date.now() > msBeforeTimeout) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`${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.`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
await new Promise((resolve) => {
|
|
78
|
-
setTimeout(resolve, STAGED_VERSION_POLL_INTERVAL_MS);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
} catch (e) {
|
|
82
|
-
waitTask.fail();
|
|
83
|
-
throw e;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const checkVersionIsInRegistry = async ({
|
|
88
|
-
registryUrl,
|
|
89
|
-
packageName,
|
|
90
|
-
packageVersion,
|
|
91
|
-
token,
|
|
92
|
-
}) => {
|
|
93
|
-
const response = await fetchRegistry(`${registryUrl}/${packageName}`, {
|
|
94
|
-
method: "GET",
|
|
95
|
-
token,
|
|
96
|
-
});
|
|
97
|
-
if (!response || response.status !== 200) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const packageObject = await response.json();
|
|
101
|
-
return Boolean(packageObject.versions[packageVersion]);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const checkTarballIsInRegistry = async ({
|
|
105
|
-
registryUrl,
|
|
106
|
-
packageName,
|
|
107
|
-
packageVersion,
|
|
108
|
-
token,
|
|
109
|
-
}) => {
|
|
110
|
-
const response = await fetchRegistry(
|
|
111
|
-
computeTarballUrl({ registryUrl, packageName, packageVersion }),
|
|
112
|
-
{ method: "HEAD", token },
|
|
113
|
-
);
|
|
114
|
-
return Boolean(response) && response.status === 200;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const computeTarballUrl = ({ registryUrl, packageName, packageVersion }) => {
|
|
118
|
-
const packageBasename =
|
|
119
|
-
packageName[0] === "@"
|
|
120
|
-
? packageName.slice(packageName.indexOf("/") + 1)
|
|
121
|
-
: packageName;
|
|
122
|
-
return `${registryUrl}/${packageName}/-/${packageBasename}-${packageVersion}.tgz`;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const fetchRegistry = async (url, { method, token }) => {
|
|
126
|
-
try {
|
|
127
|
-
return await fetch(url, {
|
|
128
|
-
method,
|
|
129
|
-
headers: {
|
|
130
|
-
"accept":
|
|
131
|
-
"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
|
|
132
|
-
// the registry is served by a cache; without this a version can stay
|
|
133
|
-
// invisible long after it landed
|
|
134
|
-
"cache-control": "no-cache",
|
|
135
|
-
...(token ? { authorization: `token ${token}` } : {}),
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
} catch {
|
|
139
|
-
// a network hiccup is one more reason for the version not to be there yet
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
};
|