@eik/core 2.1.76 → 2.1.78
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/CHANGELOG.md +14 -0
- package/lib/handlers/pkg.put.js +47 -12
- package/lib/handlers/versions.get.js +71 -45
- package/package.json +1 -1
- package/types/handlers/pkg.put.d.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.78](https://github.com/eik-lib/core/compare/v2.1.77...v2.1.78) (2026-08-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* include server-written eik.json in the package asset list ([#637](https://github.com/eik-lib/core/issues/637)) ([42ea8c6](https://github.com/eik-lib/core/commit/42ea8c6d328f2e35c2ba8618f4c3e74d48454ed2))
|
|
7
|
+
|
|
8
|
+
## [2.1.77](https://github.com/eik-lib/core/compare/v2.1.76...v2.1.77) (2026-08-17)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* propagate sink errors in versions.get as 502 rather than 404 ([#636](https://github.com/eik-lib/core/issues/636)) ([21b8619](https://github.com/eik-lib/core/commit/21b8619eeff9150914adcfa2ae6a233e2c10ec60))
|
|
14
|
+
|
|
1
15
|
## [2.1.76](https://github.com/eik-lib/core/compare/v2.1.75...v2.1.76) (2026-08-14)
|
|
2
16
|
|
|
3
17
|
|
package/lib/handlers/pkg.put.js
CHANGED
|
@@ -3,6 +3,7 @@ import originalUrl from "original-url";
|
|
|
3
3
|
import HttpError from "http-errors";
|
|
4
4
|
import Metrics from "@metrics/client";
|
|
5
5
|
import abslog from "abslog";
|
|
6
|
+
import ssri from "ssri";
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
createFilePathToPackage,
|
|
@@ -22,6 +23,7 @@ import HttpOutgoing from "../classes/http-outgoing.js";
|
|
|
22
23
|
import Versions from "../classes/versions.js";
|
|
23
24
|
import Package from "../classes/package.js";
|
|
24
25
|
import Author from "../classes/author.js";
|
|
26
|
+
import Asset from "../classes/asset.js";
|
|
25
27
|
import config from "../utils/defaults.js";
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -97,14 +99,7 @@ const PkgPut = class PkgPut {
|
|
|
97
99
|
});
|
|
98
100
|
return { pkg, eikJson };
|
|
99
101
|
})
|
|
100
|
-
.then(
|
|
101
|
-
const path = createFilePathToPackage(pkg);
|
|
102
|
-
await writeJSON(this._sink, path, pkg, "application/json");
|
|
103
|
-
|
|
104
|
-
this._log.info(
|
|
105
|
-
`pkg:put - Successfully wrote package meta file to sink - Pathname: ${path}`,
|
|
106
|
-
);
|
|
107
|
-
|
|
102
|
+
.then(({ pkg, eikJson }) => {
|
|
108
103
|
resolve({ pkg, eikJson });
|
|
109
104
|
})
|
|
110
105
|
.catch((error) => {
|
|
@@ -119,8 +114,12 @@ const PkgPut = class PkgPut {
|
|
|
119
114
|
* server-authoritative values, ensuring the file is always server-written
|
|
120
115
|
* regardless of what the client's tar contained.
|
|
121
116
|
*
|
|
117
|
+
* Returns an Asset representing the written file so the caller can include
|
|
118
|
+
* it in the package's asset list.
|
|
119
|
+
*
|
|
122
120
|
* @param {any} incoming
|
|
123
121
|
* @param {Record<string,any>|null} capturedEikJson
|
|
122
|
+
* @returns {Promise<InstanceType<typeof Asset>>}
|
|
124
123
|
*/
|
|
125
124
|
async _writeEikJson(incoming, capturedEikJson) {
|
|
126
125
|
const content = {
|
|
@@ -130,10 +129,28 @@ const PkgPut = class PkgPut {
|
|
|
130
129
|
type: incoming.type,
|
|
131
130
|
};
|
|
132
131
|
const eikJsonPath = createFilePathToEikJson(incoming);
|
|
133
|
-
await writeJSON(
|
|
132
|
+
const buffer = await writeJSON(
|
|
133
|
+
this._sink,
|
|
134
|
+
eikJsonPath,
|
|
135
|
+
content,
|
|
136
|
+
"application/json",
|
|
137
|
+
);
|
|
134
138
|
this._log.info(
|
|
135
139
|
`pkg:put - Successfully wrote eik.json to sink - Pathname: ${eikJsonPath}`,
|
|
136
140
|
);
|
|
141
|
+
|
|
142
|
+
const asset = new Asset({
|
|
143
|
+
pathname: "eik.json",
|
|
144
|
+
version: incoming.version,
|
|
145
|
+
name: incoming.name,
|
|
146
|
+
type: incoming.type,
|
|
147
|
+
org: incoming.org,
|
|
148
|
+
});
|
|
149
|
+
asset.integrity = ssri
|
|
150
|
+
.fromData(buffer, { algorithms: ["sha512"] })
|
|
151
|
+
.toString();
|
|
152
|
+
asset.size = buffer.length;
|
|
153
|
+
return asset;
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
/**
|
|
@@ -265,10 +282,26 @@ const PkgPut = class PkgPut {
|
|
|
265
282
|
});
|
|
266
283
|
|
|
267
284
|
const { pkg, eikJson } = await this._parser(incoming);
|
|
268
|
-
versions.setVersion(pVersion, pkg.integrity);
|
|
269
285
|
|
|
286
|
+
// Write eik.json before package metadata so the asset list is complete
|
|
287
|
+
// when {version}.package.json is written to the sink.
|
|
288
|
+
let eikJsonAsset;
|
|
270
289
|
try {
|
|
271
|
-
await this.
|
|
290
|
+
eikJsonAsset = await this._writeEikJson(incoming, eikJson);
|
|
291
|
+
// eslint-disable-next-line no-unused-vars
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const e = new HttpError.BadGateway();
|
|
294
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
295
|
+
throw e;
|
|
296
|
+
}
|
|
297
|
+
pkg.setAsset(eikJsonAsset);
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const pkgMetaPath = createFilePathToPackage(pkg);
|
|
301
|
+
await writeJSON(this._sink, pkgMetaPath, pkg, "application/json");
|
|
302
|
+
this._log.info(
|
|
303
|
+
`pkg:put - Successfully wrote package meta file to sink - Pathname: ${pkgMetaPath}`,
|
|
304
|
+
);
|
|
272
305
|
// eslint-disable-next-line no-unused-vars
|
|
273
306
|
} catch (error) {
|
|
274
307
|
const e = new HttpError.BadGateway();
|
|
@@ -276,8 +309,10 @@ const PkgPut = class PkgPut {
|
|
|
276
309
|
throw e;
|
|
277
310
|
}
|
|
278
311
|
|
|
312
|
+
versions.setVersion(pVersion, pkg.integrity);
|
|
313
|
+
|
|
279
314
|
try {
|
|
280
|
-
await this.
|
|
315
|
+
await this._writeVersions(incoming, versions);
|
|
281
316
|
// eslint-disable-next-line no-unused-vars
|
|
282
317
|
} catch (error) {
|
|
283
318
|
const e = new HttpError.BadGateway();
|
|
@@ -81,57 +81,83 @@ const VersionsGet = class VersionsGet {
|
|
|
81
81
|
}
|
|
82
82
|
const path = createFilePathToVersion({ org, type, name: pName });
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (this._etag) {
|
|
91
|
-
outgoing.etag = file.etag;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (this._etag && req.headers["if-none-match"] === file.etag) {
|
|
95
|
-
outgoing.statusCode = 304;
|
|
96
|
-
file.stream.destroy();
|
|
97
|
-
} else {
|
|
98
|
-
outgoing.statusCode = 200;
|
|
99
|
-
outgoing.stream = file.stream;
|
|
100
|
-
|
|
101
|
-
const outgoingStream = outgoing.stream;
|
|
102
|
-
if (outgoingStream) {
|
|
103
|
-
outgoingStream.on(
|
|
104
|
-
"error",
|
|
105
|
-
(/** @type {NodeJS.ErrnoException} */ err) => {
|
|
106
|
-
// ERR_STREAM_PREMATURE_CLOSE means the client closed the
|
|
107
|
-
// connection before the transfer finished. This is normal
|
|
108
|
-
// browser behaviour (navigation, cancelled requests, etc.)
|
|
109
|
-
// and not a server-side error worth alerting on.
|
|
110
|
-
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
111
|
-
this._log.debug(`pkg:latest - File stream error - ${err}`);
|
|
112
|
-
} else {
|
|
113
|
-
this._log.info(`pkg:latest - File stream error - ${err}`);
|
|
114
|
-
}
|
|
115
|
-
end({ labels: { success: false, status: 503, type } });
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
outgoingStream.on("end", () => {
|
|
120
|
-
end({ labels: { status: outgoing.statusCode, type } });
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
84
|
+
if (!this._sink) {
|
|
85
|
+
const e = new HttpError.NotFound();
|
|
86
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
124
89
|
|
|
125
|
-
|
|
90
|
+
// Check existence first to distinguish a genuinely missing package
|
|
91
|
+
// from a transient infrastructure error. Without this distinction,
|
|
92
|
+
// any GCS or network failure would surface as 404, which callers
|
|
93
|
+
// may misinterpret as "package does not exist".
|
|
94
|
+
try {
|
|
95
|
+
await this._sink.exist(path);
|
|
96
|
+
} catch {
|
|
97
|
+
this._log.debug(
|
|
98
|
+
`pkg:latest - Package versions not found - Pathname: ${path}`,
|
|
99
|
+
);
|
|
100
|
+
const e = new HttpError.NotFound();
|
|
101
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
// File exists — any read failure here is an infrastructure error.
|
|
106
|
+
let file;
|
|
107
|
+
try {
|
|
108
|
+
file = await this._sink.read(path);
|
|
129
109
|
} catch (error) {
|
|
130
|
-
this._log.
|
|
131
|
-
|
|
110
|
+
this._log.error(
|
|
111
|
+
`pkg:latest - Failed to read package versions from sink - Pathname: ${path}`,
|
|
112
|
+
);
|
|
113
|
+
this._log.trace(error);
|
|
114
|
+
const e = new HttpError.BadGateway();
|
|
132
115
|
end({ labels: { success: false, status: e.status, type } });
|
|
133
116
|
throw e;
|
|
134
117
|
}
|
|
118
|
+
|
|
119
|
+
const outgoing = new HttpOutgoing();
|
|
120
|
+
outgoing.cacheControl = this._cacheControl;
|
|
121
|
+
outgoing.mimeType = "application/json";
|
|
122
|
+
|
|
123
|
+
if (this._etag) {
|
|
124
|
+
outgoing.etag = file.etag;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this._etag && req.headers["if-none-match"] === file.etag) {
|
|
128
|
+
outgoing.statusCode = 304;
|
|
129
|
+
file.stream.destroy();
|
|
130
|
+
} else {
|
|
131
|
+
outgoing.statusCode = 200;
|
|
132
|
+
outgoing.stream = file.stream;
|
|
133
|
+
|
|
134
|
+
const outgoingStream = outgoing.stream;
|
|
135
|
+
if (outgoingStream) {
|
|
136
|
+
outgoingStream.on(
|
|
137
|
+
"error",
|
|
138
|
+
(/** @type {NodeJS.ErrnoException} */ err) => {
|
|
139
|
+
// ERR_STREAM_PREMATURE_CLOSE means the client closed the
|
|
140
|
+
// connection before the transfer finished. This is normal
|
|
141
|
+
// browser behaviour (navigation, cancelled requests, etc.)
|
|
142
|
+
// and not a server-side error worth alerting on.
|
|
143
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
144
|
+
this._log.debug(`pkg:latest - File stream error - ${err}`);
|
|
145
|
+
} else {
|
|
146
|
+
this._log.info(`pkg:latest - File stream error - ${err}`);
|
|
147
|
+
}
|
|
148
|
+
end({ labels: { success: false, status: 503, type } });
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
outgoingStream.on("end", () => {
|
|
153
|
+
end({ labels: { status: outgoing.statusCode, type } });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this._log.debug(`pkg:latest - Package versions found - Pathname: ${path}`);
|
|
159
|
+
|
|
160
|
+
return outgoing;
|
|
135
161
|
}
|
|
136
162
|
};
|
|
137
163
|
export default VersionsGet;
|
package/package.json
CHANGED
|
@@ -72,10 +72,14 @@ declare const PkgPut: {
|
|
|
72
72
|
* server-authoritative values, ensuring the file is always server-written
|
|
73
73
|
* regardless of what the client's tar contained.
|
|
74
74
|
*
|
|
75
|
+
* Returns an Asset representing the written file so the caller can include
|
|
76
|
+
* it in the package's asset list.
|
|
77
|
+
*
|
|
75
78
|
* @param {any} incoming
|
|
76
79
|
* @param {Record<string,any>|null} capturedEikJson
|
|
80
|
+
* @returns {Promise<InstanceType<typeof Asset>>}
|
|
77
81
|
*/
|
|
78
|
-
_writeEikJson(incoming: any, capturedEikJson: Record<string, any> | null): Promise<
|
|
82
|
+
_writeEikJson(incoming: any, capturedEikJson: Record<string, any> | null): Promise<InstanceType<typeof Asset>>;
|
|
79
83
|
/**
|
|
80
84
|
* @param {any} incoming
|
|
81
85
|
* @returns {Promise<InstanceType<typeof Versions>>}
|
|
@@ -125,4 +129,5 @@ declare const PkgPut: {
|
|
|
125
129
|
};
|
|
126
130
|
import abslog from "abslog";
|
|
127
131
|
import Metrics from "@metrics/client";
|
|
132
|
+
import Asset from "../classes/asset.js";
|
|
128
133
|
import Versions from "../classes/versions.js";
|