@eik/core 1.3.54 → 1.3.55
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/lib/classes/alias.js +48 -48
- package/lib/classes/asset.js +99 -92
- package/lib/classes/author.js +20 -20
- package/lib/classes/http-incoming.js +52 -52
- package/lib/classes/http-outgoing.js +84 -83
- package/lib/classes/meta.js +20 -23
- package/lib/classes/package.js +73 -70
- package/lib/classes/versions.js +62 -60
- package/lib/handlers/alias.delete.js +125 -120
- package/lib/handlers/alias.get.js +92 -87
- package/lib/handlers/alias.post.js +196 -203
- package/lib/handlers/alias.put.js +196 -202
- package/lib/handlers/auth.post.js +95 -93
- package/lib/handlers/map.get.js +110 -111
- package/lib/handlers/map.put.js +256 -231
- package/lib/handlers/pkg.get.js +120 -122
- package/lib/handlers/pkg.log.js +112 -110
- package/lib/handlers/pkg.put.js +223 -213
- package/lib/handlers/versions.get.js +92 -101
- package/lib/main.js +47 -47
- package/lib/multipart/form-field.js +20 -23
- package/lib/multipart/form-file.js +22 -24
- package/lib/multipart/parser.js +231 -217
- package/lib/sinks/mem-entry.js +26 -31
- package/lib/sinks/test.js +287 -273
- package/lib/utils/defaults.js +11 -11
- package/lib/utils/globals.js +5 -5
- package/lib/utils/healthcheck.js +131 -108
- package/lib/utils/path-builders-fs.js +61 -29
- package/lib/utils/path-builders-uri.js +26 -18
- package/lib/utils/utils.js +76 -79
- package/package.json +20 -15
- package/types/classes/alias.d.ts +28 -0
- package/types/classes/asset.d.ts +48 -0
- package/types/classes/author.d.ts +17 -0
- package/types/classes/http-incoming.d.ts +37 -0
- package/types/classes/http-outgoing.d.ts +20 -0
- package/types/classes/meta.d.ts +17 -0
- package/types/classes/package.d.ts +40 -0
- package/types/classes/versions.d.ts +28 -0
- package/types/handlers/alias.delete.d.ts +33 -0
- package/types/handlers/alias.get.d.ts +48 -0
- package/types/handlers/alias.post.d.ts +83 -0
- package/types/handlers/alias.put.d.ts +83 -0
- package/types/handlers/auth.post.d.ts +82 -0
- package/types/handlers/map.get.d.ts +51 -0
- package/types/handlers/map.put.d.ts +78 -0
- package/types/handlers/pkg.get.d.ts +51 -0
- package/types/handlers/pkg.log.d.ts +51 -0
- package/types/handlers/pkg.put.d.ts +107 -0
- package/types/handlers/versions.get.d.ts +48 -0
- package/types/main.d.ts +44 -0
- package/types/multipart/form-field.d.ts +17 -0
- package/types/multipart/form-file.d.ts +17 -0
- package/types/multipart/parser.d.ts +52 -0
- package/types/sinks/mem-entry.d.ts +15 -0
- package/types/sinks/test.d.ts +32 -0
- package/types/utils/defaults.d.ts +9 -0
- package/types/utils/globals.d.ts +8 -0
- package/types/utils/healthcheck.d.ts +24 -0
- package/types/utils/path-builders-fs.d.ts +41 -0
- package/types/utils/path-builders-uri.d.ts +26 -0
- package/types/utils/utils.d.ts +6 -0
package/lib/handlers/map.put.js
CHANGED
|
@@ -1,237 +1,262 @@
|
|
|
1
|
-
import { validators } from
|
|
2
|
-
import originalUrl from
|
|
3
|
-
import HttpError from
|
|
4
|
-
import Busboy from
|
|
5
|
-
import crypto from
|
|
6
|
-
import abslog from
|
|
7
|
-
import Metrics from
|
|
1
|
+
import { validators } from "@eik/common";
|
|
2
|
+
import originalUrl from "original-url";
|
|
3
|
+
import HttpError from "http-errors";
|
|
4
|
+
import Busboy from "busboy";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import abslog from "abslog";
|
|
7
|
+
import Metrics from "@metrics/client";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from
|
|
13
|
-
import { createURIPathToImportMap } from
|
|
14
|
-
import HttpIncoming from
|
|
15
|
-
import HttpOutgoing from
|
|
16
|
-
import Versions from
|
|
17
|
-
import Author from
|
|
18
|
-
import config from
|
|
19
|
-
import {
|
|
10
|
+
createFilePathToImportMap,
|
|
11
|
+
createFilePathToVersion,
|
|
12
|
+
} from "../utils/path-builders-fs.js";
|
|
13
|
+
import { createURIPathToImportMap } from "../utils/path-builders-uri.js";
|
|
14
|
+
import HttpIncoming from "../classes/http-incoming.js";
|
|
15
|
+
import HttpOutgoing from "../classes/http-outgoing.js";
|
|
16
|
+
import Versions from "../classes/versions.js";
|
|
17
|
+
import Author from "../classes/author.js";
|
|
18
|
+
import config from "../utils/defaults.js";
|
|
19
|
+
import {
|
|
20
|
+
decodeUriComponent,
|
|
21
|
+
streamCollector,
|
|
22
|
+
writeJSON,
|
|
23
|
+
readJSON,
|
|
24
|
+
} from "../utils/utils.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} MapPutOptions
|
|
28
|
+
* @property {number} [mapMaxFileSize]
|
|
29
|
+
* @property {string} [cacheControl]
|
|
30
|
+
* @property {Array<[string, string]>} [organizations] List of key-value pairs [hostname, organization]
|
|
31
|
+
* @property {import("@eik/sink").default} [sink]
|
|
32
|
+
* @property {import("abslog").AbstractLoggerOptions} [logger]
|
|
33
|
+
*/
|
|
20
34
|
|
|
21
35
|
const MapPut = class MapPut {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param {MapPutOptions} options
|
|
39
|
+
*/
|
|
40
|
+
constructor({
|
|
41
|
+
mapMaxFileSize,
|
|
42
|
+
organizations,
|
|
43
|
+
cacheControl,
|
|
44
|
+
logger,
|
|
45
|
+
sink,
|
|
46
|
+
} = {}) {
|
|
47
|
+
this._mapMaxFileSize = mapMaxFileSize || config.mapMaxFileSize;
|
|
48
|
+
this._organizations = organizations || config.organizations;
|
|
49
|
+
this._cacheControl = cacheControl;
|
|
50
|
+
this._sink = sink;
|
|
51
|
+
this._log = abslog(logger);
|
|
52
|
+
this._metrics = new Metrics();
|
|
53
|
+
this._histogram = this._metrics.histogram({
|
|
54
|
+
name: "eik_core_map_put_handler",
|
|
55
|
+
description:
|
|
56
|
+
"Histogram measuring time taken in @eik/core MapPut handler method",
|
|
57
|
+
labels: {
|
|
58
|
+
success: true,
|
|
59
|
+
type: "unknown",
|
|
60
|
+
},
|
|
61
|
+
buckets: [0.005, 0.01, 0.06, 0.1, 0.6, 1.0, 2.0, 4.0],
|
|
62
|
+
});
|
|
63
|
+
this._orgRegistry = new Map(this._organizations);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get metrics() {
|
|
67
|
+
return this._metrics;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_parser(incoming) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const path = createFilePathToImportMap(incoming);
|
|
73
|
+
const queue = [];
|
|
74
|
+
|
|
75
|
+
const busboy = Busboy({
|
|
76
|
+
headers: incoming.headers,
|
|
77
|
+
limits: {
|
|
78
|
+
fields: 0,
|
|
79
|
+
files: 1,
|
|
80
|
+
fileSize: this._mapMaxFileSize,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
busboy.on("file", (fieldname, file) => {
|
|
85
|
+
queue.push(
|
|
86
|
+
this._handleFile({
|
|
87
|
+
fieldname,
|
|
88
|
+
file,
|
|
89
|
+
path,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
busboy.on("close", () => {
|
|
95
|
+
Promise.all(queue)
|
|
96
|
+
.then((items) => {
|
|
97
|
+
// Resolve with only the first item in the array since
|
|
98
|
+
// only one uploaded file is accepted
|
|
99
|
+
resolve(items[0]);
|
|
100
|
+
})
|
|
101
|
+
.catch((error) => {
|
|
102
|
+
reject(error);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
busboy.on("error", (error) => {
|
|
107
|
+
reject(error);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// If incoming.request is handeled by stream.pipeline, it will
|
|
111
|
+
// close to early for the http framework to handle it. Let the
|
|
112
|
+
// http framework handle closing incoming.request
|
|
113
|
+
incoming.request.pipe(busboy);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async _handleFile({ fieldname, file, path }) {
|
|
118
|
+
// We accept only one file on this given fieldname.
|
|
119
|
+
// Throw if any other files is posted.
|
|
120
|
+
if (fieldname !== "map") {
|
|
121
|
+
this._log.info(
|
|
122
|
+
`map:put - Import map submitted on wrong field name - Field: ${fieldname}`,
|
|
123
|
+
);
|
|
124
|
+
throw new HttpError.BadRequest();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const hasher = crypto.createHash("sha512");
|
|
128
|
+
|
|
129
|
+
// Buffer up the incoming file and check if we can
|
|
130
|
+
// parse it as JSON or not.
|
|
131
|
+
let obj = {};
|
|
132
|
+
try {
|
|
133
|
+
const str = await streamCollector(file);
|
|
134
|
+
hasher.update(str);
|
|
135
|
+
obj = JSON.parse(str);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this._log.error(`map:put - Import map can not be parsed`);
|
|
138
|
+
this._log.trace(error);
|
|
139
|
+
throw new HttpError.UnsupportedMediaType();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Write file to storage.
|
|
143
|
+
try {
|
|
144
|
+
this._log.info(
|
|
145
|
+
`map:put - Start writing import map to sink - Pathname: ${path}`,
|
|
146
|
+
);
|
|
147
|
+
await writeJSON(this._sink, path, obj, "application/json");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this._log.error(
|
|
150
|
+
`map:put - Failed writing import map to sink - Pathname: ${path}`,
|
|
151
|
+
);
|
|
152
|
+
this._log.trace(error);
|
|
153
|
+
throw new HttpError.BadGateway();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this._log.info(
|
|
157
|
+
`map:put - Successfully wrote import map to sink - Pathname: ${path}`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return `sha512-${hasher.digest("base64")}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async _readVersions(incoming) {
|
|
164
|
+
const path = createFilePathToVersion(incoming);
|
|
165
|
+
let versions;
|
|
166
|
+
try {
|
|
167
|
+
const obj = await readJSON(this._sink, path);
|
|
168
|
+
versions = new Versions(obj);
|
|
169
|
+
this._log.info(
|
|
170
|
+
`map:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
171
|
+
);
|
|
172
|
+
// eslint-disable-next-line no-unused-vars
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// File does not exist, its probably a new package
|
|
175
|
+
versions = new Versions(incoming);
|
|
176
|
+
this._log.info(
|
|
177
|
+
`map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return versions;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async _writeVersions(incoming, versions) {
|
|
184
|
+
const path = createFilePathToVersion(incoming);
|
|
185
|
+
await writeJSON(this._sink, path, versions, "application/json");
|
|
186
|
+
this._log.info(
|
|
187
|
+
`map:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async handler(req, user, name, version) {
|
|
192
|
+
const end = this._histogram.timer();
|
|
193
|
+
|
|
194
|
+
const pVersion = decodeUriComponent(version);
|
|
195
|
+
const pName = decodeUriComponent(name);
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
validators.version(pVersion);
|
|
199
|
+
validators.name(pName);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this._log.info(`map:put - Validation failed - ${error.message}`);
|
|
202
|
+
const e = new HttpError.BadRequest();
|
|
203
|
+
end({ labels: { success: false, status: e.status } });
|
|
204
|
+
throw e;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const url = originalUrl(req);
|
|
208
|
+
const org = this._orgRegistry.get(url.hostname);
|
|
209
|
+
|
|
210
|
+
if (!org) {
|
|
211
|
+
this._log.info(
|
|
212
|
+
`map:put - Hostname does not match a configured organization - ${url.hostname}`,
|
|
213
|
+
);
|
|
214
|
+
const e = new HttpError.BadRequest();
|
|
215
|
+
end({ labels: { success: false, status: e.status, type: "map" } });
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const author = new Author(user);
|
|
220
|
+
|
|
221
|
+
const incoming = new HttpIncoming(req, {
|
|
222
|
+
type: "map",
|
|
223
|
+
version: pVersion,
|
|
224
|
+
author,
|
|
225
|
+
name: pName,
|
|
226
|
+
org,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const versions = await this._readVersions(incoming);
|
|
230
|
+
|
|
231
|
+
if (!versions.check(pVersion)) {
|
|
232
|
+
this._log.info(
|
|
233
|
+
`map:put - Semver version is lower than previous version of the package - Org: ${org} - Name: ${pName} - Version: ${pVersion}`,
|
|
234
|
+
);
|
|
235
|
+
const e = new HttpError.Conflict();
|
|
236
|
+
end({ labels: { success: false, status: e.status, type: "map" } });
|
|
237
|
+
throw e;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const integrity = await this._parser(incoming);
|
|
241
|
+
versions.setVersion(pVersion, integrity);
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
await this._writeVersions(incoming, versions);
|
|
245
|
+
// eslint-disable-next-line no-unused-vars
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const e = new HttpError.BadGateway();
|
|
248
|
+
end({ labels: { success: false, status: e.status, type: "map" } });
|
|
249
|
+
throw e;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const outgoing = new HttpOutgoing();
|
|
253
|
+
outgoing.cacheControl = this._cacheControl;
|
|
254
|
+
outgoing.statusCode = 303;
|
|
255
|
+
outgoing.location = createURIPathToImportMap(incoming);
|
|
256
|
+
|
|
257
|
+
end({ labels: { status: outgoing.statusCode, type: "map" } });
|
|
258
|
+
|
|
259
|
+
return outgoing;
|
|
260
|
+
}
|
|
236
261
|
};
|
|
237
262
|
export default MapPut;
|