@eik/core 1.3.53 → 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/CHANGELOG.md +7 -0
- 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 +22 -17
- 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/sinks/test.js
CHANGED
|
@@ -1,281 +1,295 @@
|
|
|
1
|
-
import { Writable, Readable } from
|
|
2
|
-
import { ReadFile } from
|
|
3
|
-
import Metrics from
|
|
4
|
-
import Sink from
|
|
5
|
-
import mime from
|
|
6
|
-
import path from
|
|
1
|
+
import { Writable, Readable } from "node:stream";
|
|
2
|
+
import { ReadFile } from "@eik/common";
|
|
3
|
+
import Metrics from "@metrics/client";
|
|
4
|
+
import Sink from "@eik/sink";
|
|
5
|
+
import mime from "mime";
|
|
6
|
+
import path from "node:path";
|
|
7
7
|
|
|
8
|
-
import Entry from
|
|
8
|
+
import Entry from "./mem-entry.js";
|
|
9
9
|
|
|
10
|
-
const DEFAULT_ROOT_PATH =
|
|
10
|
+
const DEFAULT_ROOT_PATH = "/eik";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @deprecated Use eik/sink-memory or implement your own. This class will be removed in a future version of core.
|
|
14
14
|
*/
|
|
15
15
|
export default class SinkTest extends Sink {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
16
|
+
constructor({ rootPath = DEFAULT_ROOT_PATH } = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this._rootPath = rootPath;
|
|
19
|
+
this._metrics = new Metrics();
|
|
20
|
+
this._state = new Map();
|
|
21
|
+
|
|
22
|
+
this._counter = this._metrics.counter({
|
|
23
|
+
name: "eik_core_sink_test",
|
|
24
|
+
description:
|
|
25
|
+
"Counter measuring access to the in memory test storage sink",
|
|
26
|
+
labels: {
|
|
27
|
+
operation: "n/a",
|
|
28
|
+
success: false,
|
|
29
|
+
access: false,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this._writeDelayResolve = (a = -1) => a;
|
|
34
|
+
this._writeDelayChunks = (a = -1) => a;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get metrics() {
|
|
38
|
+
return this._metrics;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(filePath, payload) {
|
|
42
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
43
|
+
const mimeType = mime.getType(pathname) || "application/octet-stream";
|
|
44
|
+
|
|
45
|
+
let entry;
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(payload)) {
|
|
48
|
+
entry = new Entry({ mimeType, payload });
|
|
49
|
+
} else {
|
|
50
|
+
entry = new Entry({ mimeType, payload: [payload] });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._state.set(pathname, entry);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get(filePath) {
|
|
57
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
58
|
+
if (this._state.has(pathname)) {
|
|
59
|
+
const entry = this._state.get(pathname);
|
|
60
|
+
return entry.payload.join("");
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dump() {
|
|
66
|
+
return Array.from(this._state.entries());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
load(items) {
|
|
70
|
+
if (!Array.isArray(items)) {
|
|
71
|
+
throw new Error('Argument "items" must be an Array');
|
|
72
|
+
}
|
|
73
|
+
this._state = new Map(items);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {(count: number) => number} fn
|
|
78
|
+
*/
|
|
79
|
+
set writeDelayResolve(fn) {
|
|
80
|
+
if (typeof fn !== "function") {
|
|
81
|
+
throw new TypeError("Value must be a function");
|
|
82
|
+
}
|
|
83
|
+
this._writeDelayResolve = fn;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {(count: number) => number} fn
|
|
88
|
+
*/
|
|
89
|
+
set writeDelayChunks(fn) {
|
|
90
|
+
if (typeof fn !== "function") {
|
|
91
|
+
throw new TypeError("Value must be a function");
|
|
92
|
+
}
|
|
93
|
+
this._writeDelayChunks = fn;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Common SINK API
|
|
97
|
+
|
|
98
|
+
write(filePath, contentType) {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const operation = "write";
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
Sink.validateFilePath(filePath);
|
|
104
|
+
Sink.validateContentType(contentType);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this._counter.inc({ labels: { operation } });
|
|
107
|
+
reject(error);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
112
|
+
|
|
113
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
114
|
+
this._counter.inc({ labels: { operation } });
|
|
115
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const chunkDelay = this._writeDelayChunks;
|
|
120
|
+
const payload = [];
|
|
121
|
+
let count = 0;
|
|
122
|
+
const stream = new Writable({
|
|
123
|
+
write(chunk, encoding, cb) {
|
|
124
|
+
const timeout = chunkDelay(count);
|
|
125
|
+
count += 1;
|
|
126
|
+
|
|
127
|
+
if (timeout < 0) {
|
|
128
|
+
payload.push(chunk);
|
|
129
|
+
cb();
|
|
130
|
+
} else {
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
payload.push(chunk);
|
|
133
|
+
cb();
|
|
134
|
+
}, timeout);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
stream.on("finish", () => {
|
|
140
|
+
const entry = new Entry({
|
|
141
|
+
mimeType: contentType,
|
|
142
|
+
payload,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this._state.set(pathname, entry);
|
|
146
|
+
|
|
147
|
+
this._counter.inc({
|
|
148
|
+
labels: {
|
|
149
|
+
success: true,
|
|
150
|
+
access: true,
|
|
151
|
+
operation,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const resolveDelay = this._writeDelayResolve();
|
|
157
|
+
if (resolveDelay < 0) {
|
|
158
|
+
resolve(stream);
|
|
159
|
+
} else {
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
resolve(stream);
|
|
162
|
+
}, resolveDelay);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
read(filePath) {
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
const operation = "read";
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
Sink.validateFilePath(filePath);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this._counter.inc({ labels: { operation } });
|
|
175
|
+
reject(error);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
180
|
+
|
|
181
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
182
|
+
this._counter.inc({ labels: { operation } });
|
|
183
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const entry = this._state.get(pathname);
|
|
188
|
+
const payload = entry.payload || [];
|
|
189
|
+
const file = new ReadFile({
|
|
190
|
+
mimeType: entry.mimeType,
|
|
191
|
+
etag: entry.hash,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
file.stream = new Readable({
|
|
195
|
+
read() {
|
|
196
|
+
payload.forEach((item) => {
|
|
197
|
+
this.push(item);
|
|
198
|
+
});
|
|
199
|
+
this.push(null);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
file.stream.on("end", () => {
|
|
204
|
+
this._counter.inc({
|
|
205
|
+
labels: {
|
|
206
|
+
success: true,
|
|
207
|
+
access: true,
|
|
208
|
+
operation,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
resolve(file);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
delete(filePath) {
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
const operation = "delete";
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
Sink.validateFilePath(filePath);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this._counter.inc({ labels: { operation } });
|
|
225
|
+
reject(error);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
230
|
+
|
|
231
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
232
|
+
this._counter.inc({ labels: { operation } });
|
|
233
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Delete recursively
|
|
238
|
+
Array.from(this._state.keys()).forEach((key) => {
|
|
239
|
+
if (key.startsWith(pathname)) {
|
|
240
|
+
this._state.delete(key);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
this._counter.inc({
|
|
245
|
+
labels: {
|
|
246
|
+
success: true,
|
|
247
|
+
access: true,
|
|
248
|
+
operation,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
resolve();
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
exist(filePath) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const operation = "exist";
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
Sink.validateFilePath(filePath);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
this._counter.inc({ labels: { operation } });
|
|
264
|
+
reject(error);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const pathname = path.join(this._rootPath, filePath);
|
|
269
|
+
|
|
270
|
+
if (pathname.indexOf(this._rootPath) !== 0) {
|
|
271
|
+
this._counter.inc({ labels: { operation } });
|
|
272
|
+
reject(new Error(`Directory traversal - ${filePath}`));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this._counter.inc({
|
|
277
|
+
labels: {
|
|
278
|
+
success: true,
|
|
279
|
+
access: true,
|
|
280
|
+
operation,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (this._state.has(pathname)) {
|
|
285
|
+
resolve();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
reject(new Error("File does not exist"));
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
get [Symbol.toStringTag]() {
|
|
293
|
+
return "SinkTest";
|
|
294
|
+
}
|
|
281
295
|
}
|
package/lib/utils/defaults.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import os from
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
3
|
|
|
4
4
|
const config = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
authKey: "change_me",
|
|
6
|
+
pkgMaxFileSize: 10000000,
|
|
7
|
+
mapMaxFileSize: 1000000,
|
|
8
|
+
sinkFsRootPath: path.join(os.tmpdir(), "/eik-files"),
|
|
9
|
+
etag: true,
|
|
10
|
+
organizations: /** @type {Array<[string, string]>} */ ([
|
|
11
|
+
["localhost", "local"],
|
|
12
|
+
["127.0.0.1", "local"],
|
|
13
|
+
]),
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export default config;
|
package/lib/utils/globals.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const globals = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
BASE_AUTHENTICATION: "auth",
|
|
3
|
+
BASE_IMPORT_MAPS: "map",
|
|
4
|
+
BASE_PACKAGES: "pkg",
|
|
5
|
+
BASE_NPM: "npm",
|
|
6
|
+
ROOT: "/",
|
|
7
7
|
};
|
|
8
8
|
export default globals;
|