@eik/core 2.1.72 → 2.1.74
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 +39 -4
- package/lib/multipart/form-file.js +8 -2
- package/lib/multipart/parser.js +39 -2
- package/lib/utils/utils.js +2 -2
- package/package.json +1 -1
- package/types/handlers/pkg.put.d.ts +10 -0
- package/types/multipart/form-file.d.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.74](https://github.com/eik-lib/core/compare/v2.1.73...v2.1.74) (2026-08-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* prevent process crash on second busboy error and fix UTF-8 chunk corruption ([#633](https://github.com/eik-lib/core/issues/633)) ([9f5e3ec](https://github.com/eik-lib/core/commit/9f5e3ecab5ea8ceed16a0826c0c9c1824e68beac)), closes [#9](https://github.com/eik-lib/core/issues/9) [#11](https://github.com/eik-lib/core/issues/11)
|
|
7
|
+
|
|
8
|
+
## [2.1.73](https://github.com/eik-lib/core/compare/v2.1.72...v2.1.73) (2026-08-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* write eik.json server-side as the final committed marker for package uploads ([#632](https://github.com/eik-lib/core/issues/632)) ([4461f76](https://github.com/eik-lib/core/commit/4461f76881886992db70848d42c7d6a199397c8f)), closes [#4](https://github.com/eik-lib/core/issues/4) [#5](https://github.com/eik-lib/core/issues/5)
|
|
14
|
+
|
|
1
15
|
## [2.1.72](https://github.com/eik-lib/core/compare/v2.1.71...v2.1.72) (2026-08-14)
|
|
2
16
|
|
|
3
17
|
|
package/lib/handlers/pkg.put.js
CHANGED
|
@@ -82,19 +82,22 @@ const PkgPut = class PkgPut {
|
|
|
82
82
|
.parse(incoming)
|
|
83
83
|
.then((result) => {
|
|
84
84
|
const pkg = new Package(incoming);
|
|
85
|
+
/** @type {Record<string,any>|null} */
|
|
86
|
+
let eikJson = null;
|
|
85
87
|
result.forEach((/** @type {any} */ obj) => {
|
|
86
88
|
if (obj.constructor.name === "FormField") {
|
|
87
89
|
pkg.setMeta(obj);
|
|
88
90
|
}
|
|
89
91
|
if (obj.constructor.name === "FormFile") {
|
|
92
|
+
eikJson = obj.eikJson;
|
|
90
93
|
obj.value.forEach((/** @type {any} */ o) => {
|
|
91
94
|
pkg.setAsset(o);
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
});
|
|
95
|
-
return pkg;
|
|
98
|
+
return { pkg, eikJson };
|
|
96
99
|
})
|
|
97
|
-
.then(async (pkg) => {
|
|
100
|
+
.then(async ({ pkg, eikJson }) => {
|
|
98
101
|
const path = createFilePathToPackage(pkg);
|
|
99
102
|
await writeJSON(this._sink, path, pkg, "application/json");
|
|
100
103
|
|
|
@@ -102,7 +105,7 @@ const PkgPut = class PkgPut {
|
|
|
102
105
|
`pkg:put - Successfully wrote package meta file to sink - Pathname: ${path}`,
|
|
103
106
|
);
|
|
104
107
|
|
|
105
|
-
resolve(pkg);
|
|
108
|
+
resolve({ pkg, eikJson });
|
|
106
109
|
})
|
|
107
110
|
.catch((error) => {
|
|
108
111
|
reject(error);
|
|
@@ -110,6 +113,29 @@ const PkgPut = class PkgPut {
|
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Writes eik.json to the sink as the final committed marker for a version.
|
|
118
|
+
* Merges any fields from the client's eik.json (captured from the tar) with
|
|
119
|
+
* server-authoritative values, ensuring the file is always server-written
|
|
120
|
+
* regardless of what the client's tar contained.
|
|
121
|
+
*
|
|
122
|
+
* @param {any} incoming
|
|
123
|
+
* @param {Record<string,any>|null} capturedEikJson
|
|
124
|
+
*/
|
|
125
|
+
async _writeEikJson(incoming, capturedEikJson) {
|
|
126
|
+
const content = {
|
|
127
|
+
...capturedEikJson,
|
|
128
|
+
name: incoming.name,
|
|
129
|
+
version: incoming.version,
|
|
130
|
+
type: incoming.type,
|
|
131
|
+
};
|
|
132
|
+
const eikJsonPath = createFilePathToEikJson(incoming);
|
|
133
|
+
await writeJSON(this._sink, eikJsonPath, content, "application/json");
|
|
134
|
+
this._log.info(
|
|
135
|
+
`pkg:put - Successfully wrote eik.json to sink - Pathname: ${eikJsonPath}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
113
139
|
/**
|
|
114
140
|
* @param {any} incoming
|
|
115
141
|
*/
|
|
@@ -226,7 +252,7 @@ const PkgPut = class PkgPut {
|
|
|
226
252
|
}
|
|
227
253
|
|
|
228
254
|
const versions = await this._readVersions(incoming);
|
|
229
|
-
const pkg = await this._parser(incoming);
|
|
255
|
+
const { pkg, eikJson } = await this._parser(incoming);
|
|
230
256
|
versions.setVersion(pVersion, pkg.integrity);
|
|
231
257
|
|
|
232
258
|
try {
|
|
@@ -238,6 +264,15 @@ const PkgPut = class PkgPut {
|
|
|
238
264
|
throw e;
|
|
239
265
|
}
|
|
240
266
|
|
|
267
|
+
try {
|
|
268
|
+
await this._writeEikJson(incoming, eikJson);
|
|
269
|
+
// eslint-disable-next-line no-unused-vars
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const e = new HttpError.BadGateway();
|
|
272
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
const outgoing = new HttpOutgoing();
|
|
242
277
|
outgoing.cacheControl = this._cacheControl || "";
|
|
243
278
|
outgoing.statusCode = 303;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
const FormFile = class FormFile {
|
|
2
2
|
/**
|
|
3
|
-
* @param {{ value?: any[], name?: string }} [options]
|
|
3
|
+
* @param {{ value?: any[], name?: string, eikJson?: Record<string,any>|null }} [options]
|
|
4
4
|
*/
|
|
5
|
-
constructor({ value = [], name = "" } = {}) {
|
|
5
|
+
constructor({ value = [], name = "", eikJson = null } = {}) {
|
|
6
6
|
if (!Array.isArray(value))
|
|
7
7
|
throw new TypeError('The argument "value" must be of type Array');
|
|
8
8
|
this._value = value;
|
|
9
9
|
this._name = name;
|
|
10
|
+
this._eikJson = eikJson;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
get value() {
|
|
@@ -17,6 +18,11 @@ const FormFile = class FormFile {
|
|
|
17
18
|
return this._name;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/** @returns {Record<string,any>|null} */
|
|
22
|
+
get eikJson() {
|
|
23
|
+
return this._eikJson;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
toJSON() {
|
|
21
27
|
return {
|
|
22
28
|
value: this.value,
|
package/lib/multipart/parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pipeline } from "node:stream";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import HttpError from "http-errors";
|
|
3
4
|
import Busboy from "busboy";
|
|
4
5
|
import abslog from "abslog";
|
|
@@ -105,7 +106,13 @@ const MultipartParser = class MultipartParser {
|
|
|
105
106
|
},
|
|
106
107
|
);
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
// Use .on() instead of .once() so that a second error emitted
|
|
110
|
+
// by a concurrent _handleFile failure (after the first error has
|
|
111
|
+
// already removed a .once() listener) does not hit zero listeners
|
|
112
|
+
// and crash the process with an unhandled EventEmitter throw.
|
|
113
|
+
// Calling reject() a second time is a safe no-op on an already-
|
|
114
|
+
// settled Promise.
|
|
115
|
+
busboy.on("error", (/** @type {any} */ error) => {
|
|
109
116
|
reject(error);
|
|
110
117
|
});
|
|
111
118
|
|
|
@@ -148,6 +155,8 @@ const MultipartParser = class MultipartParser {
|
|
|
148
155
|
const ac = new AbortController();
|
|
149
156
|
/** @type {Promise<any>[]} */
|
|
150
157
|
const queue = [];
|
|
158
|
+
/** @type {Record<string,any>|null} */
|
|
159
|
+
let capturedEikJson = null;
|
|
151
160
|
|
|
152
161
|
file.once("limit", () => {
|
|
153
162
|
this._log.warn(
|
|
@@ -166,6 +175,30 @@ const MultipartParser = class MultipartParser {
|
|
|
166
175
|
entry.resume();
|
|
167
176
|
return;
|
|
168
177
|
}
|
|
178
|
+
|
|
179
|
+
// eik.json is captured in memory and written server-side
|
|
180
|
+
// after the upload is fully committed, so that it can serve
|
|
181
|
+
// as a reliable committed marker independent of what the
|
|
182
|
+
// client includes in the tar.
|
|
183
|
+
if (path.normalize(entry.path) === "eik.json") {
|
|
184
|
+
/** @type {Buffer[]} */
|
|
185
|
+
const chunks = [];
|
|
186
|
+
entry.on("data", (/** @type {Buffer} */ chunk) => {
|
|
187
|
+
chunks.push(chunk);
|
|
188
|
+
});
|
|
189
|
+
entry.once("end", () => {
|
|
190
|
+
try {
|
|
191
|
+
capturedEikJson = JSON.parse(
|
|
192
|
+
Buffer.concat(chunks).toString("utf8"),
|
|
193
|
+
);
|
|
194
|
+
} catch {
|
|
195
|
+
// Malformed eik.json in tar — proceed without it.
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
entry.once("error", () => {});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
const persist = this._persistFile({
|
|
170
203
|
incoming,
|
|
171
204
|
entry,
|
|
@@ -205,7 +238,11 @@ const MultipartParser = class MultipartParser {
|
|
|
205
238
|
|
|
206
239
|
Promise.all(queue)
|
|
207
240
|
.then((result) => {
|
|
208
|
-
const formFile = new FormFile({
|
|
241
|
+
const formFile = new FormFile({
|
|
242
|
+
name: fieldname,
|
|
243
|
+
value: result,
|
|
244
|
+
eikJson: capturedEikJson,
|
|
245
|
+
});
|
|
209
246
|
resolve(formFile);
|
|
210
247
|
})
|
|
211
248
|
.catch((err) => {
|
package/lib/utils/utils.js
CHANGED
|
@@ -22,7 +22,7 @@ const readJSON = (sink, path) =>
|
|
|
22
22
|
|
|
23
23
|
pipeline(from.stream, to, (error) => {
|
|
24
24
|
if (error) return reject(error);
|
|
25
|
-
const str =
|
|
25
|
+
const str = Buffer.concat(buffer).toString("utf8");
|
|
26
26
|
try {
|
|
27
27
|
const obj = JSON.parse(str);
|
|
28
28
|
return resolve(obj);
|
|
@@ -86,7 +86,7 @@ const streamCollector = (from) =>
|
|
|
86
86
|
|
|
87
87
|
pipeline(from, to, (error) => {
|
|
88
88
|
if (error) return reject(error);
|
|
89
|
-
return resolve(
|
|
89
|
+
return resolve(Buffer.concat(buffer).toString("utf8"));
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
|
package/package.json
CHANGED
|
@@ -66,6 +66,16 @@ declare const PkgPut: {
|
|
|
66
66
|
* @param {any} incoming
|
|
67
67
|
*/
|
|
68
68
|
_parser(incoming: any): Promise<any>;
|
|
69
|
+
/**
|
|
70
|
+
* Writes eik.json to the sink as the final committed marker for a version.
|
|
71
|
+
* Merges any fields from the client's eik.json (captured from the tar) with
|
|
72
|
+
* server-authoritative values, ensuring the file is always server-written
|
|
73
|
+
* regardless of what the client's tar contained.
|
|
74
|
+
*
|
|
75
|
+
* @param {any} incoming
|
|
76
|
+
* @param {Record<string,any>|null} capturedEikJson
|
|
77
|
+
*/
|
|
78
|
+
_writeEikJson(incoming: any, capturedEikJson: Record<string, any> | null): Promise<void>;
|
|
69
79
|
/**
|
|
70
80
|
* @param {any} incoming
|
|
71
81
|
*/
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export default FormFile;
|
|
2
2
|
declare const FormFile: {
|
|
3
|
-
new ({ value, name }?: {
|
|
3
|
+
new ({ value, name, eikJson }?: {
|
|
4
4
|
value?: any[];
|
|
5
5
|
name?: string;
|
|
6
|
+
eikJson?: Record<string, any> | null;
|
|
6
7
|
}): {
|
|
7
8
|
_value: any[];
|
|
8
9
|
_name: string;
|
|
10
|
+
_eikJson: Record<string, any> | null;
|
|
9
11
|
get value(): any[];
|
|
10
12
|
get name(): string;
|
|
13
|
+
/** @returns {Record<string,any>|null} */
|
|
14
|
+
get eikJson(): Record<string, any> | null;
|
|
11
15
|
toJSON(): {
|
|
12
16
|
value: any[];
|
|
13
17
|
name: string;
|