@sasjs/api 0.6.0 → 0.6.1
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/build/browser.js +19 -6
- package/build/cjs/drive/deploy.js +49 -10
- package/build/esm/drive/deploy.d.ts +10 -0
- package/build/esm/drive/deploy.d.ts.map +1 -1
- package/build/esm/drive/deploy.js +48 -9
- package/package.json +1 -1
package/build/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @sasjs/api 0.6.
|
|
1
|
+
/* @sasjs/api 0.6.1 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
|
|
2
2
|
"use strict";
|
|
3
3
|
var SASjsApi = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -111,6 +111,7 @@ var SASjsApi = (() => {
|
|
|
111
111
|
status: "success",
|
|
112
112
|
message: "Files deployed successfully to @sasjs/server."
|
|
113
113
|
};
|
|
114
|
+
var MAX_DEPTH = 32;
|
|
114
115
|
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
115
116
|
var base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, "");
|
|
116
117
|
var base64Length = (value) => Math.floor(base64Body(value).length * 3 / 4);
|
|
@@ -145,7 +146,9 @@ var SASjsApi = (() => {
|
|
|
145
146
|
};
|
|
146
147
|
var memberName = (member) => member.type === "service" ? `${member.name}.sas` : member.name;
|
|
147
148
|
var memberBytes = (member) => member.type === "file" ? base64Length(member.code) : utf8Length(member.code);
|
|
148
|
-
var
|
|
149
|
+
var MEGABYTE = 1024 * 1024;
|
|
150
|
+
var describeSize = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.ceil(bytes2 / MEGABYTE * 10) / 10} MB`;
|
|
151
|
+
var describeLimit = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.round(bytes2 / MEGABYTE * 10) / 10} MB`;
|
|
149
152
|
var emptyWalk = () => ({
|
|
150
153
|
entries: 0,
|
|
151
154
|
totalBytes: 0,
|
|
@@ -153,7 +156,7 @@ var SASjsApi = (() => {
|
|
|
153
156
|
});
|
|
154
157
|
var isSafeSegment = (name) => name !== "" && name !== "." && name !== ".." && !/[/\\]/.test(name) && // a Windows drive or a NUL byte is never a segment either
|
|
155
158
|
!/[\0]/.test(name);
|
|
156
|
-
var walk = (members, parentPath, acc) => {
|
|
159
|
+
var walk = (members, parentPath, acc, depth = 1) => {
|
|
157
160
|
for (const member of members) {
|
|
158
161
|
const name = memberName(member);
|
|
159
162
|
const memberPath = parentPath ? `${parentPath}/${name}` : name;
|
|
@@ -165,7 +168,11 @@ var SASjsApi = (() => {
|
|
|
165
168
|
};
|
|
166
169
|
acc.entries++;
|
|
167
170
|
if (member.type === "folder") {
|
|
168
|
-
|
|
171
|
+
if (depth + 1 > MAX_DEPTH) {
|
|
172
|
+
if (!acc.tooDeep) acc.tooDeep = depth + 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
walk(member.members, memberPath, acc, depth + 1);
|
|
169
176
|
continue;
|
|
170
177
|
}
|
|
171
178
|
const bytes2 = memberBytes(member);
|
|
@@ -186,6 +193,12 @@ var SASjsApi = (() => {
|
|
|
186
193
|
const measured = emptyWalk();
|
|
187
194
|
walk(members, "", measured);
|
|
188
195
|
if (measured.nameRefusal) return measured.nameRefusal;
|
|
196
|
+
if (measured.tooDeep)
|
|
197
|
+
return {
|
|
198
|
+
code: 413,
|
|
199
|
+
status: "failure",
|
|
200
|
+
message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${MAX_DEPTH}).`
|
|
201
|
+
};
|
|
189
202
|
if (measured.entries > limits.entries)
|
|
190
203
|
return {
|
|
191
204
|
code: 413,
|
|
@@ -196,13 +209,13 @@ var SASjsApi = (() => {
|
|
|
196
209
|
return {
|
|
197
210
|
code: 413,
|
|
198
211
|
status: "failure",
|
|
199
|
-
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${
|
|
212
|
+
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
|
|
200
213
|
};
|
|
201
214
|
if (measured.largest.bytes > limits.fileBytes)
|
|
202
215
|
return {
|
|
203
216
|
code: 413,
|
|
204
217
|
status: "failure",
|
|
205
|
-
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${
|
|
218
|
+
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
|
|
206
219
|
};
|
|
207
220
|
return null;
|
|
208
221
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deployTree = exports.isDeployTree = exports.checkDeployTree = exports.measureDeployTree = exports.decodeBase64 = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = void 0;
|
|
3
|
+
exports.deployTree = exports.isDeployTree = exports.checkDeployTree = exports.measureDeployTree = exports.decodeBase64 = exports.MAX_DEPTH = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = void 0;
|
|
4
4
|
const types_js_1 = require("../types.js");
|
|
5
5
|
const paths_js_1 = require("./paths.js");
|
|
6
6
|
exports.DEPLOY_LIMITS = {
|
|
@@ -36,6 +36,16 @@ exports.DEPLOY_SUCCESS = {
|
|
|
36
36
|
status: 'success',
|
|
37
37
|
message: 'Files deployed successfully to @sasjs/server.'
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* How deep a tree may nest.
|
|
41
|
+
*
|
|
42
|
+
* A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
|
|
43
|
+
* levels is generous, and the number matters because the walk recurses once per
|
|
44
|
+
* level - without a ceiling, a tree nested deeply enough exhausts the stack and
|
|
45
|
+
* throws a `RangeError`, which is neither the `400` nor the `413` this route
|
|
46
|
+
* documents, on input the cap exists to bound.
|
|
47
|
+
*/
|
|
48
|
+
exports.MAX_DEPTH = 32;
|
|
39
49
|
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
40
50
|
/** A base64 body without its padding, which is what the arithmetic needs. */
|
|
41
51
|
const base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, '');
|
|
@@ -82,14 +92,28 @@ const utf8Length = (value) => {
|
|
|
82
92
|
const memberName = (member) => member.type === 'service' ? `${member.name}.sas` : member.name;
|
|
83
93
|
/** The bytes a member writes. */
|
|
84
94
|
const memberBytes = (member) => member.type === 'file' ? base64Length(member.code) : utf8Length(member.code);
|
|
95
|
+
const MEGABYTE = 1024 * 1024;
|
|
85
96
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
97
|
+
* A MEASURED size, rounded UP to one decimal in binary megabytes.
|
|
98
|
+
*
|
|
99
|
+
* Up, not to nearest: a tree one byte over a 100 MB cap must not read as "100 MB
|
|
100
|
+
* (maximum 100 MB)", which is a refusal that states no overage at all. Rounding
|
|
101
|
+
* up is the direction that never understates what is being refused.
|
|
102
|
+
*
|
|
103
|
+
* Bytes below a megabyte are printed as bytes, because a limit small enough to
|
|
104
|
+
* be measured in bytes should not read as "0 MB" - which is what a host passing
|
|
105
|
+
* a tighter set would otherwise report.
|
|
89
106
|
*/
|
|
90
|
-
const describeSize = (bytes) => bytes <
|
|
107
|
+
const describeSize = (bytes) => bytes < MEGABYTE
|
|
91
108
|
? `${bytes} bytes`
|
|
92
|
-
: `${Math.
|
|
109
|
+
: `${Math.ceil((bytes / MEGABYTE) * 10) / 10} MB`;
|
|
110
|
+
/**
|
|
111
|
+
* A LIMIT, printed as it is stated: `DEPLOY_LIMITS` holds exact megabytes, so
|
|
112
|
+
* rounding cannot make it disagree with itself.
|
|
113
|
+
*/
|
|
114
|
+
const describeLimit = (bytes) => bytes < MEGABYTE
|
|
115
|
+
? `${bytes} bytes`
|
|
116
|
+
: `${Math.round((bytes / MEGABYTE) * 10) / 10} MB`;
|
|
93
117
|
/** A walk's starting point. */
|
|
94
118
|
const emptyWalk = () => ({
|
|
95
119
|
entries: 0,
|
|
@@ -113,8 +137,12 @@ const isSafeSegment = (name) => name !== '' &&
|
|
|
113
137
|
* a caller that only wants the numbers gets the size of the whole tree, and the
|
|
114
138
|
* validator decides whether the name matters. Returning early would report a
|
|
115
139
|
* truncated tree as if it were the real one.
|
|
140
|
+
*
|
|
141
|
+
* The one thing that stops the walk is depth: past `MAX_DEPTH` it does not
|
|
142
|
+
* descend, because descending is the work that would exhaust the stack. The
|
|
143
|
+
* level it reached is recorded so the refusal can state it.
|
|
116
144
|
*/
|
|
117
|
-
const walk = (members, parentPath, acc) => {
|
|
145
|
+
const walk = (members, parentPath, acc, depth = 1) => {
|
|
118
146
|
for (const member of members) {
|
|
119
147
|
const name = memberName(member);
|
|
120
148
|
const memberPath = parentPath ? `${parentPath}/${name}` : name;
|
|
@@ -126,7 +154,12 @@ const walk = (members, parentPath, acc) => {
|
|
|
126
154
|
};
|
|
127
155
|
acc.entries++;
|
|
128
156
|
if (member.type === 'folder') {
|
|
129
|
-
|
|
157
|
+
if (depth + 1 > exports.MAX_DEPTH) {
|
|
158
|
+
if (!acc.tooDeep)
|
|
159
|
+
acc.tooDeep = depth + 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
walk(member.members, memberPath, acc, depth + 1);
|
|
130
163
|
continue;
|
|
131
164
|
}
|
|
132
165
|
const bytes = memberBytes(member);
|
|
@@ -166,6 +199,12 @@ const checkDeployTree = (members, limits = exports.DEPLOY_LIMITS) => {
|
|
|
166
199
|
walk(members, '', measured);
|
|
167
200
|
if (measured.nameRefusal)
|
|
168
201
|
return measured.nameRefusal;
|
|
202
|
+
if (measured.tooDeep)
|
|
203
|
+
return {
|
|
204
|
+
code: 413,
|
|
205
|
+
status: 'failure',
|
|
206
|
+
message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${exports.MAX_DEPTH}).`
|
|
207
|
+
};
|
|
169
208
|
if (measured.entries > limits.entries)
|
|
170
209
|
return {
|
|
171
210
|
code: 413,
|
|
@@ -176,13 +215,13 @@ const checkDeployTree = (members, limits = exports.DEPLOY_LIMITS) => {
|
|
|
176
215
|
return {
|
|
177
216
|
code: 413,
|
|
178
217
|
status: 'failure',
|
|
179
|
-
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${
|
|
218
|
+
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
|
|
180
219
|
};
|
|
181
220
|
if (measured.largest.bytes > limits.fileBytes)
|
|
182
221
|
return {
|
|
183
222
|
code: 413,
|
|
184
223
|
status: 'failure',
|
|
185
|
-
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${
|
|
224
|
+
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
|
|
186
225
|
};
|
|
187
226
|
return null;
|
|
188
227
|
};
|
|
@@ -99,6 +99,16 @@ export interface DeployMeasurements {
|
|
|
99
99
|
bytes: number;
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* How deep a tree may nest.
|
|
104
|
+
*
|
|
105
|
+
* A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
|
|
106
|
+
* levels is generous, and the number matters because the walk recurses once per
|
|
107
|
+
* level - without a ceiling, a tree nested deeply enough exhausts the stack and
|
|
108
|
+
* throws a `RangeError`, which is neither the `400` nor the `413` this route
|
|
109
|
+
* documents, on input the cap exists to bound.
|
|
110
|
+
*/
|
|
111
|
+
export declare const MAX_DEPTH = 32;
|
|
102
112
|
/** The bytes a base64 body decodes to, for a store that writes bytes. */
|
|
103
113
|
export declare const decodeBase64: (value: string) => Uint8Array;
|
|
104
114
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/drive/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAI7C,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,YAAY,GACtB,kBAAkB,GAAG,mBAAmB,GAAG,gBAAgB,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,EAAE,YAI3B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAoB,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAA;AAEV,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;IACf,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/drive/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAI7C,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,YAAY,GACtB,kBAAkB,GAAG,mBAAmB,GAAG,gBAAgB,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,EAAE,YAI3B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAoB,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAA;AAEV,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;IACf,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AASD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,KAAK,CAAA;AAY3B,yEAAyE;AACzE,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,UAmB5C,CAAA;AA8HD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,YAAY,EAAE,KACtB,kBAUF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EAAE,EACvB,SAAQ,YAA4B,KACnC,aAAa,GAAG,IAoClB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAMtD,CAAA;AAkBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,UAAU,EACjB,QAAQ,OAAO,EACf,MAAM,OAAO,EACb,SAAQ,YAA4B,KACnC,OAAO,CAAC,IAAI,CAiCd,CAAA"}
|
|
@@ -33,6 +33,16 @@ export const DEPLOY_SUCCESS = {
|
|
|
33
33
|
status: 'success',
|
|
34
34
|
message: 'Files deployed successfully to @sasjs/server.'
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* How deep a tree may nest.
|
|
38
|
+
*
|
|
39
|
+
* A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
|
|
40
|
+
* levels is generous, and the number matters because the walk recurses once per
|
|
41
|
+
* level - without a ceiling, a tree nested deeply enough exhausts the stack and
|
|
42
|
+
* throws a `RangeError`, which is neither the `400` nor the `413` this route
|
|
43
|
+
* documents, on input the cap exists to bound.
|
|
44
|
+
*/
|
|
45
|
+
export const MAX_DEPTH = 32;
|
|
36
46
|
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
37
47
|
/** A base64 body without its padding, which is what the arithmetic needs. */
|
|
38
48
|
const base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, '');
|
|
@@ -78,14 +88,28 @@ const utf8Length = (value) => {
|
|
|
78
88
|
const memberName = (member) => member.type === 'service' ? `${member.name}.sas` : member.name;
|
|
79
89
|
/** The bytes a member writes. */
|
|
80
90
|
const memberBytes = (member) => member.type === 'file' ? base64Length(member.code) : utf8Length(member.code);
|
|
91
|
+
const MEGABYTE = 1024 * 1024;
|
|
81
92
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
93
|
+
* A MEASURED size, rounded UP to one decimal in binary megabytes.
|
|
94
|
+
*
|
|
95
|
+
* Up, not to nearest: a tree one byte over a 100 MB cap must not read as "100 MB
|
|
96
|
+
* (maximum 100 MB)", which is a refusal that states no overage at all. Rounding
|
|
97
|
+
* up is the direction that never understates what is being refused.
|
|
98
|
+
*
|
|
99
|
+
* Bytes below a megabyte are printed as bytes, because a limit small enough to
|
|
100
|
+
* be measured in bytes should not read as "0 MB" - which is what a host passing
|
|
101
|
+
* a tighter set would otherwise report.
|
|
85
102
|
*/
|
|
86
|
-
const describeSize = (bytes) => bytes <
|
|
103
|
+
const describeSize = (bytes) => bytes < MEGABYTE
|
|
87
104
|
? `${bytes} bytes`
|
|
88
|
-
: `${Math.
|
|
105
|
+
: `${Math.ceil((bytes / MEGABYTE) * 10) / 10} MB`;
|
|
106
|
+
/**
|
|
107
|
+
* A LIMIT, printed as it is stated: `DEPLOY_LIMITS` holds exact megabytes, so
|
|
108
|
+
* rounding cannot make it disagree with itself.
|
|
109
|
+
*/
|
|
110
|
+
const describeLimit = (bytes) => bytes < MEGABYTE
|
|
111
|
+
? `${bytes} bytes`
|
|
112
|
+
: `${Math.round((bytes / MEGABYTE) * 10) / 10} MB`;
|
|
89
113
|
/** A walk's starting point. */
|
|
90
114
|
const emptyWalk = () => ({
|
|
91
115
|
entries: 0,
|
|
@@ -109,8 +133,12 @@ const isSafeSegment = (name) => name !== '' &&
|
|
|
109
133
|
* a caller that only wants the numbers gets the size of the whole tree, and the
|
|
110
134
|
* validator decides whether the name matters. Returning early would report a
|
|
111
135
|
* truncated tree as if it were the real one.
|
|
136
|
+
*
|
|
137
|
+
* The one thing that stops the walk is depth: past `MAX_DEPTH` it does not
|
|
138
|
+
* descend, because descending is the work that would exhaust the stack. The
|
|
139
|
+
* level it reached is recorded so the refusal can state it.
|
|
112
140
|
*/
|
|
113
|
-
const walk = (members, parentPath, acc) => {
|
|
141
|
+
const walk = (members, parentPath, acc, depth = 1) => {
|
|
114
142
|
for (const member of members) {
|
|
115
143
|
const name = memberName(member);
|
|
116
144
|
const memberPath = parentPath ? `${parentPath}/${name}` : name;
|
|
@@ -122,7 +150,12 @@ const walk = (members, parentPath, acc) => {
|
|
|
122
150
|
};
|
|
123
151
|
acc.entries++;
|
|
124
152
|
if (member.type === 'folder') {
|
|
125
|
-
|
|
153
|
+
if (depth + 1 > MAX_DEPTH) {
|
|
154
|
+
if (!acc.tooDeep)
|
|
155
|
+
acc.tooDeep = depth + 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
walk(member.members, memberPath, acc, depth + 1);
|
|
126
159
|
continue;
|
|
127
160
|
}
|
|
128
161
|
const bytes = memberBytes(member);
|
|
@@ -161,6 +194,12 @@ export const checkDeployTree = (members, limits = DEPLOY_LIMITS) => {
|
|
|
161
194
|
walk(members, '', measured);
|
|
162
195
|
if (measured.nameRefusal)
|
|
163
196
|
return measured.nameRefusal;
|
|
197
|
+
if (measured.tooDeep)
|
|
198
|
+
return {
|
|
199
|
+
code: 413,
|
|
200
|
+
status: 'failure',
|
|
201
|
+
message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${MAX_DEPTH}).`
|
|
202
|
+
};
|
|
164
203
|
if (measured.entries > limits.entries)
|
|
165
204
|
return {
|
|
166
205
|
code: 413,
|
|
@@ -171,13 +210,13 @@ export const checkDeployTree = (members, limits = DEPLOY_LIMITS) => {
|
|
|
171
210
|
return {
|
|
172
211
|
code: 413,
|
|
173
212
|
status: 'failure',
|
|
174
|
-
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${
|
|
213
|
+
message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
|
|
175
214
|
};
|
|
176
215
|
if (measured.largest.bytes > limits.fileBytes)
|
|
177
216
|
return {
|
|
178
217
|
code: 413,
|
|
179
218
|
status: 'failure',
|
|
180
|
-
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${
|
|
219
|
+
message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
|
|
181
220
|
};
|
|
182
221
|
return null;
|
|
183
222
|
};
|
package/package.json
CHANGED