arcadible 0.1.27 → 0.1.29

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/package.json CHANGED
@@ -61,5 +61,5 @@
61
61
  "license": "Apache-2.0",
62
62
  "main": "./src/cli/index.js",
63
63
  "name": "arcadible",
64
- "version": "0.1.27"
64
+ "version": "0.1.29"
65
65
  }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ // [export]
4
+
5
+ module.exports = ["unused", "generative"];
package/src/config/art.js CHANGED
@@ -11,6 +11,6 @@ module.exports = [
11
11
  "icon_video.mp4",
12
12
  "poster.png",
13
13
  "poster_video.mp4",
14
- "wordmark.svg",
15
- "wordmark_animated.svg",
14
+ "splash.png",
15
+ "splash_video.mp4",
16
16
  ];
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ // [export]
4
+
5
+ module.exports = {
6
+ ".html": "text/html",
7
+ };
@@ -3,25 +3,19 @@
3
3
  // [export]
4
4
 
5
5
  module.exports = {
6
- ALLOWED_ATTR: ["href", "target", "rel", "src", "alt", "title"],
7
- ALLOWED_TAGS: [
8
- "h3",
9
- "h4",
10
- "h5",
11
- "h6",
12
- "hr",
13
- "p",
14
- "ul",
15
- "ol",
16
- "li",
17
- "pre",
18
- "img",
19
- "a",
20
- "code",
21
- "strong",
22
- "b",
23
- "em",
24
- "i",
25
- "br",
26
- ],
6
+ allowed: {
7
+ a: ["href", "title"],
8
+ blockquote: [],
9
+ em: [],
10
+ h3: [],
11
+ h4: [],
12
+ hr: [],
13
+ img: ["src", "alt", "title"],
14
+ li: [],
15
+ ol: [],
16
+ p: [],
17
+ strong: [],
18
+ ul: [],
19
+ video: ["src", "alt", "title"],
20
+ },
27
21
  };
@@ -4,5 +4,4 @@
4
4
 
5
5
  module.exports = {
6
6
  ".png": "image/png",
7
- ".svg": "image/svg+xml",
8
7
  };
@@ -2,4 +2,4 @@
2
2
 
3
3
  // [export]
4
4
 
5
- module.exports = ["private", "unlisted", "public"];
5
+ module.exports = ["unlisted", "public"];
package/src/phrase.js CHANGED
@@ -34,6 +34,36 @@ const cmdValidate = function () {
34
34
 
35
35
  // [function]
36
36
 
37
+ const docAttrInvalid = function (tag, attr, value) {
38
+ return `Value '${value}' is not valid for the '${attr}' attribute on the '${tag}' tag.`;
39
+ };
40
+
41
+ // [function]
42
+
43
+ const docAttrUnallowed = function (tag, attr) {
44
+ return `Attribute '${attr}' is not permitted on the '${tag}' tag.`;
45
+ };
46
+
47
+ // [function]
48
+
49
+ const docSanitizeFailed = function () {
50
+ return "Document contains invalid elements.";
51
+ };
52
+
53
+ // [function]
54
+
55
+ const docTagUnallowed = function (tag) {
56
+ return `Tag '${tag}' is not permitted.`;
57
+ };
58
+
59
+ // [function]
60
+
61
+ const docUnsupported = function () {
62
+ return "Document has an unsupported file type.";
63
+ };
64
+
65
+ // [function]
66
+
37
67
  const gameExists = function (idOrHandle) {
38
68
  return `Game exists: ${idOrHandle}`;
39
69
  };
@@ -355,8 +385,11 @@ const videoWidthMin = function (width) {
355
385
  module.exports = {
356
386
  cmdInit,
357
387
  cmdInspect,
358
- cmdLogin,
359
- cmdValidate,
388
+ docAttrInvalid,
389
+ docAttrUnallowed,
390
+ docSanitizeFailed,
391
+ docTagUnallowed,
392
+ docUnsupported,
360
393
  gameExists,
361
394
  gameVersionExists,
362
395
  gitChangesPresent,
@@ -10,6 +10,7 @@ const fs = require("fs-extra");
10
10
 
11
11
  // [import]
12
12
 
13
+ const docCheck = require("../../util/doc").check;
13
14
  const imageCheck = require("../../util/image").check;
14
15
  const videoCheck = require("../../util/video").check;
15
16
 
@@ -23,6 +24,12 @@ const resolve = path.resolve;
23
24
 
24
25
  // [function]
25
26
 
27
+ const doc = function (root, str, opts) {
28
+ return docCheck(str, opts);
29
+ };
30
+
31
+ // [function]
32
+
26
33
  const exists = function (root, target) {
27
34
  return pathExistsSync(join(root, target));
28
35
  };
@@ -64,4 +71,12 @@ const video = function (root, target, opts) {
64
71
 
65
72
  // [export]
66
73
 
67
- module.exports = { exists, image, isIn, isOut, isRelative, video };
74
+ module.exports = {
75
+ doc,
76
+ exists,
77
+ image,
78
+ isIn,
79
+ isOut,
80
+ isRelative,
81
+ video,
82
+ };
@@ -8,7 +8,9 @@ const z = require("zod");
8
8
 
9
9
  // [import]
10
10
 
11
+ const cfgai = require("../config/ai");
11
12
  const cfgaudience = require("../config/audience");
13
+ const cfgdoc = require("../config/doc");
12
14
  const cfggenre = require("../config/genre");
13
15
  const cfghtml = require("../config/html");
14
16
  const cfgimage = require("../config/image");
@@ -19,28 +21,41 @@ const cfgurl = require("../config/url");
19
21
  const cfgvideo = require("../config/video");
20
22
  const cfgvisibility = require("../config/visibility");
21
23
  const check = require("./check/game");
22
- const dom = require("../util/dom");
24
+ const paths = require("../util/paths");
23
25
  const phrase = require("../phrase");
24
26
  const size = require("../util/size");
25
27
  const strs = require("../util/strs");
28
+ const uris = require("../util/uris");
26
29
 
27
30
  // [use]
28
31
 
32
+ const basefile = paths.basefile;
29
33
  const cleanSemver = semver.clean;
34
+ const docCheck = check.doc;
30
35
  const existsCheck = check.exists;
31
36
  const imageCheck = check.image;
32
37
  const indentLines = strs.indentLines;
33
38
  const isInCheck = check.isIn;
34
39
  const isOutCheck = check.isOut;
35
40
  const isRelativeCheck = check.isRelative;
41
+ const isSafeUri = uris.isSafeUri;
36
42
  const validSemver = semver.valid;
37
43
  const videoCheck = check.video;
38
44
 
39
45
  // [declare]
40
46
 
41
47
  const HANDLE = cfgproduct.handle;
48
+
49
+ // [declare]
50
+
51
+ const GALLERY = `.${HANDLE}/gallery`;
42
52
  const MEDIA = `.${HANDLE}`;
43
53
 
54
+ // [declare]
55
+
56
+ const MAX_SIZE_MP4 = size.mb(15);
57
+ const MAX_SIZE_PNG = size.mb(2);
58
+
44
59
  // [function]
45
60
 
46
61
  const reference = function (field) {
@@ -61,15 +76,62 @@ const message = function (field, lines) {
61
76
 
62
77
  // [function]
63
78
 
64
- const zText = function (field, { max, min }) {
79
+ const zSet = function (field, schema, { max, min }) {
80
+ return z
81
+ .array(schema)
82
+ .min(min, message(field, [`Must be at least: ${min} element`]))
83
+ .max(max, message(field, [`Must not exceed: ${max} elements`]))
84
+ .refine(
85
+ (items) => new Set(items).size === items.length,
86
+ message(field, ["Must not contain duplicate elements."]),
87
+ );
88
+ };
89
+
90
+ // [function]
91
+
92
+ const zSetBasefile = function (field, schema, { max, min }) {
93
+ return z
94
+ .array(schema)
95
+ .min(min, message(field, [`Must be at least: ${min} file`]))
96
+ .max(max, message(field, [`Must not exceed: ${max} files`]))
97
+ .refine(
98
+ (items) => new Set(items.map(basefile)).size === items.length,
99
+ message(field, [
100
+ "Must not contain duplicate filenames (ignoring extensions).",
101
+ ]),
102
+ );
103
+ };
104
+
105
+ // [function]
106
+
107
+ const zString = function (field, re, { max, min }) {
65
108
  return z
66
109
  .string()
67
110
  .min(min, message(field, [`Must be at least: ${min} character`]))
68
111
  .max(max, message(field, [`Must not exceed: ${max} characters`]))
69
- .regex(
70
- /^[\p{L}0-9 .,!?'"():;-]+$/u,
71
- message(field, ["Contains invalid characters."]),
72
- );
112
+ .regex(re, message(field, ["Contains invalid characters."]));
113
+ };
114
+
115
+ // [function]
116
+
117
+ const zText = function (field, opts) {
118
+ const re = /^[\p{L}0-9 .,!?'"():;-]+$/u;
119
+ return zString(field, re, opts);
120
+ };
121
+
122
+ // [function]
123
+
124
+ const zHtml = function (field, root, { max, min, ...opts }) {
125
+ const re = /^[\p{L}\p{M}\p{N}\p{P}\p{S}\p{Zs}\r\n]*$/u;
126
+ return zString(field, re, { max, min }).superRefine((str, ctx) => {
127
+ const issues = docCheck(root, str, { ...opts, type: cfgdoc[".html"] });
128
+ if (issues.length) {
129
+ ctx.addIssue({
130
+ code: "custom",
131
+ ...message(field, ["Invalid Markdown.", ...issues]),
132
+ });
133
+ }
134
+ });
73
135
  };
74
136
 
75
137
  // [function]
@@ -109,13 +171,18 @@ const zPng = function (field, root, opts) {
109
171
 
110
172
  // [function]
111
173
 
112
- const zSvg = function (field, root, opts) {
174
+ const zMp4 = function (field, root, opts) {
113
175
  return zMedia(field, root).superRefine((file, ctx) => {
114
- const issues = imageCheck(root, file, { ...opts, type: cfgimage[".svg"] });
176
+ const issues = videoCheck(root, file, {
177
+ maxDuration: 30,
178
+ minDuration: 3,
179
+ ...opts,
180
+ type: cfgvideo[".mp4"],
181
+ });
115
182
  if (issues.length) {
116
183
  ctx.addIssue({
117
184
  code: "custom",
118
- ...message(field, ["Invalid SVG.", ...issues]),
185
+ ...message(field, ["Invalid MP4.", ...issues]),
119
186
  });
120
187
  }
121
188
  });
@@ -123,19 +190,27 @@ const zSvg = function (field, root, opts) {
123
190
 
124
191
  // [function]
125
192
 
126
- const zMp4 = function (field, root, opts) {
127
- return zMedia(field, root).superRefine((file, ctx) => {
128
- const issues = videoCheck(root, file, {
129
- maxDuration: 30,
130
- minDuration: 3,
131
- ...opts,
132
- type: cfgvideo[".mp4"],
133
- });
134
- if (issues.length) {
193
+ const zPngMp4 = function (field, root, optsPng, optsMp4) {
194
+ const pngSchema = zPng(field, root, optsPng);
195
+ const mp4Schema = zMp4(field, root, optsMp4);
196
+ return z.string().superRefine((file, ctx) => {
197
+ let schema = null;
198
+ if (file?.endsWith(".png")) {
199
+ schema = pngSchema;
200
+ }
201
+ if (file?.endsWith(".mp4")) {
202
+ schema = mp4Schema;
203
+ }
204
+ if (!schema) {
135
205
  ctx.addIssue({
136
206
  code: "custom",
137
- ...message(field, ["Invalid MP4.", ...issues]),
207
+ ...message(field, ["Must be a PNG or MP4 file."]),
138
208
  });
209
+ return;
210
+ }
211
+ const result = schema.safeParse(file);
212
+ if (!result.success) {
213
+ result.error.issues.forEach((issue) => ctx.addIssue(issue));
139
214
  }
140
215
  });
141
216
  };
@@ -162,6 +237,13 @@ const zYouTube = function () {
162
237
 
163
238
  // [function]
164
239
 
240
+ const zAi = function () {
241
+ // const field = "ai";
242
+ return z.optional(z.enum(cfgai));
243
+ };
244
+
245
+ // [function]
246
+
165
247
  const zAudience = function () {
166
248
  // const field = "audience";
167
249
  return z.optional(z.enum(cfgaudience));
@@ -174,7 +256,7 @@ const zBanner = function (root) {
174
256
  return z.optional(
175
257
  zPng(field, root, {
176
258
  aspect: "2:1",
177
- maxSize: size.mb(2),
259
+ maxSize: MAX_SIZE_PNG,
178
260
  minHeight: 512,
179
261
  minWidth: 1024,
180
262
  }),
@@ -188,7 +270,7 @@ const zBannerVideo = function (root) {
188
270
  return z.optional(
189
271
  zMp4(field, root, {
190
272
  aspect: "2:1",
191
- maxSize: size.mb(15),
273
+ maxSize: MAX_SIZE_MP4,
192
274
  minHeight: 512,
193
275
  minWidth: 1024,
194
276
  }),
@@ -209,7 +291,7 @@ const zCover = function (root) {
209
291
  return z.optional(
210
292
  zPng(field, root, {
211
293
  aspect: "3:2",
212
- maxSize: size.mb(2),
294
+ maxSize: MAX_SIZE_PNG,
213
295
  minHeight: 512,
214
296
  minWidth: 768,
215
297
  }),
@@ -223,7 +305,7 @@ const zCoverVideo = function (root) {
223
305
  return z.optional(
224
306
  zMp4(field, root, {
225
307
  aspect: "3:2",
226
- maxSize: size.mb(15),
308
+ maxSize: MAX_SIZE_MP4,
227
309
  minHeight: 512,
228
310
  minWidth: 768,
229
311
  }),
@@ -232,22 +314,22 @@ const zCoverVideo = function (root) {
232
314
 
233
315
  // [function]
234
316
 
235
- const zDescription = function () {
317
+ const zDescription = function (root) {
236
318
  const field = "description";
237
319
  return z.optional(
238
- z
239
- .string()
240
- .min(1, message(field, ["Must be at least: 1 character"]))
241
- .max(3000, message(field, ["Must not exceed: 3000 characters"]))
242
- .refine(
243
- (str) =>
244
- str ===
245
- dom.sanitize(str, {
246
- ALLOWED_ATTR: cfghtml.ALLOWED_ATTR,
247
- ALLOWED_TAGS: cfghtml.ALLOWED_TAGS,
248
- }),
249
- message(field, ["Contains invalid elements."]),
250
- ),
320
+ zHtml(field, root, {
321
+ allowed: cfghtml.allowed,
322
+ hooks: {
323
+ href: (uri) => isSafeUri(uri),
324
+ src: (src) =>
325
+ isRelativeCheck(root, src) &&
326
+ isOutCheck(root, src) &&
327
+ isInCheck(root, src, GALLERY) &&
328
+ existsCheck(root, src),
329
+ },
330
+ max: 3000,
331
+ min: 1,
332
+ }),
251
333
  );
252
334
  };
253
335
 
@@ -256,48 +338,26 @@ const zDescription = function () {
256
338
  const zGallery = function (root) {
257
339
  const field = "gallery";
258
340
  return z.optional(
259
- z
260
- .array(
261
- zMedia(field, root).superRefine((file, ctx) => {
262
- if (file.endsWith(".png")) {
263
- const issues = imageCheck(root, file, {
264
- maxSize: size.mb(2),
265
- type: cfgimage[".png"],
266
- });
267
- if (issues.length) {
268
- ctx.addIssue({
269
- code: "custom",
270
- ...message(field, [`Invalid PNG: ${file}`, ...issues]),
271
- });
272
- }
273
- } else if (file.endsWith(".svg")) {
274
- const issues = imageCheck(root, file, {
275
- maxSize: size.mb(1),
276
- type: cfgimage[".svg"],
277
- });
278
- if (issues.length) {
279
- ctx.addIssue({
280
- code: "custom",
281
- ...message(field, [`Invalid SVG: ${file}`, ...issues]),
282
- });
283
- }
284
- } else if (file.endsWith(".mp4")) {
285
- const issues = videoCheck(root, file, {
286
- maxDuration: 30,
287
- maxSize: size.mb(15),
288
- minDuration: 3,
289
- type: cfgvideo[".mp4"],
290
- });
291
- if (issues.length) {
292
- ctx.addIssue({
293
- code: "custom",
294
- ...message(field, [`Invalid MP4: ${file}`, ...issues]),
295
- });
296
- }
297
- }
298
- }),
299
- )
300
- .max(12, message(field, ["Must not exceed: 12 elements"])),
341
+ zSetBasefile(
342
+ field,
343
+ zPngMp4(
344
+ field,
345
+ root,
346
+ // png
347
+ {
348
+ maxSize: MAX_SIZE_PNG,
349
+ minHeight: 512,
350
+ minWidth: 512,
351
+ },
352
+ // mp4
353
+ {
354
+ maxSize: MAX_SIZE_MP4,
355
+ minHeight: 512,
356
+ minWidth: 512,
357
+ },
358
+ ),
359
+ { max: 8 * 3, min: 1 },
360
+ ),
301
361
  );
302
362
  };
303
363
 
@@ -305,11 +365,7 @@ const zGallery = function (root) {
305
365
 
306
366
  const zGenres = function () {
307
367
  const field = "genres";
308
- return z.optional(
309
- z
310
- .array(z.enum(cfggenre))
311
- .max(3, message(field, ["Must not exceed: 3 elements"])),
312
- );
368
+ return z.optional(zSet(field, z.enum(cfggenre), { max: 3, min: 1 }));
313
369
  };
314
370
 
315
371
  // [function]
@@ -333,7 +389,7 @@ const zIcon = function (root) {
333
389
  return z.optional(
334
390
  zPng(field, root, {
335
391
  aspect: "1:1",
336
- maxSize: size.mb(2),
392
+ maxSize: MAX_SIZE_PNG,
337
393
  minHeight: 512,
338
394
  minWidth: 512,
339
395
  }),
@@ -347,7 +403,7 @@ const zIconVideo = function (root) {
347
403
  return z.optional(
348
404
  zMp4(field, root, {
349
405
  aspect: "1:1",
350
- maxSize: size.mb(15),
406
+ maxSize: MAX_SIZE_MP4,
351
407
  minHeight: 512,
352
408
  minWidth: 512,
353
409
  }),
@@ -376,11 +432,7 @@ const zId = function () {
376
432
 
377
433
  const zInputs = function () {
378
434
  const field = "inputs";
379
- return z.optional(
380
- z
381
- .array(z.enum(cfginput))
382
- .max(3, message(field, ["Must not exceed: 3 elements"])),
383
- );
435
+ return z.optional(zSet(field, z.enum(cfginput), { max: 3, min: 1 }));
384
436
  };
385
437
 
386
438
  // [function]
@@ -397,7 +449,7 @@ const zPoster = function (root) {
397
449
  return z.optional(
398
450
  zPng(field, root, {
399
451
  aspect: "2:3",
400
- maxSize: size.mb(2),
452
+ maxSize: MAX_SIZE_PNG,
401
453
  minHeight: 768,
402
454
  minWidth: 512,
403
455
  }),
@@ -411,7 +463,7 @@ const zPosterVideo = function (root) {
411
463
  return z.optional(
412
464
  zMp4(field, root, {
413
465
  aspect: "2:3",
414
- maxSize: size.mb(15),
466
+ maxSize: MAX_SIZE_MP4,
415
467
  minHeight: 768,
416
468
  minWidth: 512,
417
469
  }),
@@ -439,6 +491,34 @@ const zShortTitle = function () {
439
491
 
440
492
  // [function]
441
493
 
494
+ const zSplash = function (root) {
495
+ const field = "splash";
496
+ return z.optional(
497
+ zPng(field, root, {
498
+ aspect: "16:9",
499
+ maxSize: MAX_SIZE_PNG,
500
+ minHeight: 576,
501
+ minWidth: 1024,
502
+ }),
503
+ );
504
+ };
505
+
506
+ // [function]
507
+
508
+ const zSplashVideo = function (root) {
509
+ const field = "splash_video";
510
+ return z.optional(
511
+ zMp4(field, root, {
512
+ aspect: "16:9",
513
+ maxSize: MAX_SIZE_MP4,
514
+ minHeight: 576,
515
+ minWidth: 1024,
516
+ }),
517
+ );
518
+ };
519
+
520
+ // [function]
521
+
442
522
  const zTagline = function () {
443
523
  const field = "tagline";
444
524
  return z.optional(zText(field, { max: 30 * 1, min: 1 }));
@@ -496,38 +576,17 @@ const zVisibility = function () {
496
576
 
497
577
  // [function]
498
578
 
499
- const zWordmark = function (root) {
500
- const field = "wordmark";
501
- return z.optional(
502
- zSvg(field, root, {
503
- maxSize: size.kb(512),
504
- }),
505
- );
506
- };
507
-
508
- // [function]
509
-
510
- const zWordmarkAnimated = function (root) {
511
- const field = "wordmark_animated";
512
- return z.optional(
513
- zSvg(field, root, {
514
- maxSize: size.mb(1),
515
- }),
516
- );
517
- };
518
-
519
- // [function]
520
-
521
579
  const main = function (value, { root }) {
522
580
  /* eslint-disable camelcase */
523
581
  return z.object({
582
+ ai: zAi(),
524
583
  audience: zAudience(),
525
584
  banner: zBanner(root),
526
585
  banner_video: zBannerVideo(root),
527
586
  bio: zBio(),
528
587
  cover: zCover(root),
529
588
  cover_video: zCoverVideo(root),
530
- description: zDescription(),
589
+ description: zDescription(root),
531
590
  gallery: zGallery(root),
532
591
  genres: zGenres(),
533
592
  handle: zHandle(),
@@ -540,13 +599,13 @@ const main = function (value, { root }) {
540
599
  poster_video: zPosterVideo(root),
541
600
  price: zPrice(),
542
601
  short_title: zShortTitle(),
602
+ splash: zSplash(root),
603
+ splash_video: zSplashVideo(root),
543
604
  tagline: zTagline(),
544
605
  title: zTitle(),
545
606
  trailer: zTrailer(),
546
607
  version: zVersion(),
547
608
  visibility: zVisibility(),
548
- wordmark: zWordmark(root),
549
- wordmark_animated: zWordmarkAnimated(root),
550
609
  });
551
610
  /* eslint-enable camelcase */
552
611
  };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ // [import]
4
+
5
+ const cfgdoc = require("../config/doc");
6
+ const dom = require("./dom");
7
+ const phrase = require("../phrase");
8
+
9
+ // [function]
10
+
11
+ const checkAttr = function (allowed, hooks, issues) {
12
+ return (node, event) => {
13
+ const nodeName = node.nodeName.toLowerCase();
14
+ const attrName = event.attrName;
15
+ const attrValue = event.attrValue;
16
+ const isAllowed = allowed[nodeName]?.includes(attrName);
17
+ const isValid = !hooks[attrName] || hooks[attrName](attrValue);
18
+ if (!isAllowed || !isValid) {
19
+ issues.push({ attrName, attrValue, isAllowed, isValid, nodeName });
20
+ event.keepAttr = false;
21
+ }
22
+ };
23
+ };
24
+
25
+ // [function]
26
+
27
+ const checkAllowed = function (str, allowed, hooks, issues) {
28
+ const attr = checkAttr(allowed, hooks, issues);
29
+ return str === dom.sanitize(str, { allowed, hooks: { attr } });
30
+ };
31
+
32
+ // [function]
33
+
34
+ const checkHtml = function (str, { allowed, hooks }) {
35
+ const issues = [];
36
+ if (!checkAllowed(str, allowed, hooks, issues)) {
37
+ if (issues) {
38
+ return issues.map((issue) => {
39
+ const { attrName, attrValue, isAllowed, isValid, nodeName } = issue;
40
+ if (!isAllowed) {
41
+ return Object.hasOwn(allowed, nodeName)
42
+ ? phrase.docAttrUnallowed(nodeName, attrName)
43
+ : phrase.docTagUnallowed(nodeName);
44
+ }
45
+ return isValid
46
+ ? phrase.docSanitizeFailed()
47
+ : phrase.docAttrInvalid(nodeName, attrName, attrValue);
48
+ });
49
+ }
50
+ return [phrase.docSanitizeFailed()];
51
+ }
52
+ return [];
53
+ };
54
+
55
+ // [function]
56
+
57
+ const check = function (str, { type, ...opts }) {
58
+ const checks = { [cfgdoc[".html"]]: checkHtml };
59
+ const checkDoc = checks[type];
60
+ return checkDoc ? checkDoc(str, opts) : [phrase.docUnsupported()];
61
+ };
62
+
63
+ // [export]
64
+
65
+ module.exports = { check };
package/src/util/dom.js CHANGED
@@ -11,17 +11,36 @@ const JSDOM = jsdom.JSDOM;
11
11
 
12
12
  // [function]
13
13
 
14
- const sanitize = function (str, cfg) {
15
- const dom = dompurify(new JSDOM().window);
16
- if (cfg) {
14
+ const sanitizeCustom = function (dom, str, cfg) {
15
+ const allowed = cfg.allowed;
16
+ const hooks = cfg.hooks;
17
+ if (hooks?.attr) {
18
+ dom.addHook("uponSanitizeAttribute", hooks.attr);
19
+ }
20
+ if (allowed) {
21
+ const attrs = new Set(Object.values(allowed).flatMap((arr) => arr));
22
+ const tags = Object.keys(allowed);
17
23
  return dom.sanitize(str, {
18
- ALLOWED_ATTR: cfg.ALLOWED_ATTR,
19
- ALLOWED_TAGS: cfg.ALLOWED_TAGS,
24
+ ALLOWED_ATTR: attrs,
25
+ ALLOWED_TAGS: tags,
20
26
  });
21
27
  }
22
28
  return dom.sanitize(str);
23
29
  };
24
30
 
31
+ // [function]
32
+
33
+ const sanitizeBasic = function (dom, str) {
34
+ return dom.sanitize(str);
35
+ };
36
+
37
+ // [function]
38
+
39
+ const sanitize = function (str, cfg) {
40
+ const dom = dompurify(new JSDOM().window);
41
+ return cfg ? sanitizeCustom(dom, str, cfg) : sanitizeBasic(dom, str);
42
+ };
43
+
25
44
  // [export]
26
45
 
27
46
  module.exports = { DOM: JSDOM, sanitize };
package/src/util/image.js CHANGED
@@ -71,19 +71,9 @@ const checkPng = function (file, { aspect, maxSize, minHeight, minWidth }) {
71
71
 
72
72
  // [function]
73
73
 
74
- const checkSvg = function (file, { maxSize }) {
75
- if (!checkSize(file, maxSize)) {
76
- return [phrase.imageSizeMax(formatBytes(maxSize))];
77
- }
78
- return [];
79
- };
80
-
81
- // [function]
82
-
83
74
  const check = function (file, { type, ...opts }) {
84
75
  const checks = {
85
76
  [cfgimage[".png"]]: checkPng,
86
- [cfgimage[".svg"]]: checkSvg,
87
77
  };
88
78
  const checkImage = checks[type];
89
79
  return checkImage && cfgimage[extname(file)] === type
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ // [import]
4
+
5
+ const path = require("node:path");
6
+
7
+ // [use]
8
+
9
+ const extname = path.extname;
10
+
11
+ // [function]
12
+
13
+ const basefile = function (file) {
14
+ return file.slice(0, -extname(file).length);
15
+ };
16
+
17
+ // [export]
18
+
19
+ module.exports = { basefile };
@@ -4,6 +4,10 @@
4
4
 
5
5
  const chalk = require("chalk");
6
6
 
7
+ // [import]
8
+
9
+ const cfgurl = require("../config/url");
10
+
7
11
  // [function]
8
12
 
9
13
  const dereference = function (issues) {
@@ -12,7 +16,7 @@ const dereference = function (issues) {
12
16
  issue
13
17
  .split("\n")
14
18
  .filter((line) => {
15
- if (/https?:\/\//u.test(line)) {
19
+ if (new RegExp(`${cfgurl.MANIFEST}`, "u").test(line)) {
16
20
  references.add(line.trim());
17
21
  return false;
18
22
  }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ // [function]
4
+
5
+ const isSafeUri = function (uri) {
6
+ return /^(?:(?:https):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/iu.test(uri);
7
+ };
8
+
9
+ // [export]
10
+
11
+ module.exports = { isSafeUri };