arcadible 0.1.22 → 0.1.24

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/bin/arc-login.js CHANGED
@@ -68,9 +68,9 @@ const onLoginEnter = function (fn) {
68
68
 
69
69
  const handler = function ({ browser, qrcode: qr, sw }) {
70
70
  return {
71
- onLoginCreated({ code: dac, id, url }) {
71
+ onLoginCreated({ code: dac, secret, url }) {
72
72
  sw.stop(true);
73
- const link = `${url}/${id}`;
73
+ const link = `${url}/${secret}`;
74
74
  log.p("Logging in...");
75
75
  qrcode.generate(link, { small: true }, (code) => {
76
76
  if (qr) {
@@ -17,10 +17,12 @@ const cfgproduct = require("../src/config/product");
17
17
  const log = require("../src/util/log");
18
18
  const output = require("../src/util/output");
19
19
  const rc = require("../src/util/rc");
20
+ const schema = require("../src/util/schema");
20
21
  const validate = require("../src/cli/validate");
21
22
 
22
23
  // [use]
23
24
 
25
+ const dereference = schema.dereference;
24
26
  const findRc = rc.findRc;
25
27
  const print = output.print;
26
28
  const resolve = path.resolve;
@@ -49,8 +51,17 @@ const start = function (argv) {
49
51
  if (json) {
50
52
  print({ issues });
51
53
  } else if (count) {
52
- log.hd(`${count} ${count === 1 ? "issue" : "issues"} found.`);
53
- log.p(issues.map((issue) => `✗ ${issue}`));
54
+ const [probs, refs] = dereference(issues);
55
+ const probcount = probs.length;
56
+ if (probcount) {
57
+ log.hd(`${probcount} ${probcount === 1 ? "issue" : "issues"}:`);
58
+ log.p(probs.map((prob) => `✗ ${prob}`));
59
+ }
60
+ const refcount = refs.length;
61
+ if (refcount) {
62
+ log.h(`${refcount} ${refcount === 1 ? "reference" : "references"}:`);
63
+ log.p(refs);
64
+ }
54
65
  } else if (!argv.quiet) {
55
66
  log.hs("No issues found.");
56
67
  }
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.22"
64
+ "version": "0.1.24"
65
65
  }
@@ -2,22 +2,12 @@
2
2
 
3
3
  // [import]
4
4
 
5
+ const cfgart = require("../../config/art");
5
6
  const cfgproduct = require("../../config/product");
6
7
 
7
8
  // [declare]
8
9
 
9
- const ART = [
10
- "banner",
11
- "banner_video",
12
- "cover",
13
- "cover_video",
14
- "icon",
15
- "icon_video",
16
- "poster",
17
- "poster_video",
18
- "wordmark",
19
- "wordmark_animated",
20
- ];
10
+ const ART = cfgart.map((file) => file.split(".")[0]);
21
11
 
22
12
  // [declare]
23
13
 
@@ -20,10 +20,10 @@ const randomUUID = crypto.randomUUID;
20
20
  // [declare]
21
21
 
22
22
  const POLL = {
23
- delay: time.s(3),
24
- factor: 2,
25
- interval: time.s(30),
26
- maxDelay: time.s(15),
23
+ delay: time.s(1.5),
24
+ factor: 1.5,
25
+ interval: time.s(15),
26
+ maxDelay: time.s(5),
27
27
  timeoutError: phrase.loginExpired(),
28
28
  };
29
29
 
@@ -48,7 +48,7 @@ const flow = function ({ id, secret: token }) {
48
48
  const createFlow = function ({ expiresAt, secret, type }, onLoginCreated) {
49
49
  return postFlow({ expiresAt, secret, type }).then(({ code, id }) => {
50
50
  if (onLoginCreated) {
51
- onLoginCreated({ code, id, url: cfgurl.LINK });
51
+ onLoginCreated({ code, secret, url: cfgurl.LINK });
52
52
  }
53
53
  return { id };
54
54
  });
@@ -77,7 +77,7 @@ const pollFlow = function (value, onLoginPoll) {
77
77
  // [function]
78
78
 
79
79
  const main = function ({ onLoginCreated, onLoginPoll }) {
80
- const expiresAt = new Date(Date.now() + time.m(5));
80
+ const expiresAt = new Date(Date.now() + time.m(10));
81
81
  const secret = randomUUID();
82
82
  return createFlow(
83
83
  {
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ // [export]
4
+
5
+ module.exports = [
6
+ "banner.png",
7
+ "banner_video.mp4",
8
+ "cover.png",
9
+ "cover_video.mp4",
10
+ "icon.png",
11
+ "icon_video.mp4",
12
+ "poster.png",
13
+ "poster_video.mp4",
14
+ "wordmark.svg",
15
+ "wordmark_animated.svg",
16
+ ];
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ // [export]
4
+
5
+ module.exports = {
6
+ ".png": "image/png",
7
+ ".svg": "image/svg+xml",
8
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ // [export]
4
+
5
+ module.exports = {
6
+ ".mp4": "video/mp4",
7
+ };
@@ -10,7 +10,10 @@ const fs = require("fs-extra");
10
10
 
11
11
  // [import]
12
12
 
13
+ const cfgart = require("../config/art");
14
+ const cfgimage = require("../config/image");
13
15
  const cfgproduct = require("../config/product");
16
+ const cfgvideo = require("../config/video");
14
17
  const objs = require("../util/objs");
15
18
 
16
19
  // [use]
@@ -24,18 +27,7 @@ const readdir = fs.promises.readdir;
24
27
 
25
28
  // [declare]
26
29
 
27
- const ART = [
28
- "banner.png",
29
- "banner_video.mp4",
30
- "cover.png",
31
- "cover_video.mp4",
32
- "icon.png",
33
- "icon_video.mp4",
34
- "poster.png",
35
- "poster_video.mp4",
36
- "wordmark.svg",
37
- "wordmark_animated.svg",
38
- ];
30
+ const ART = cfgart;
39
31
 
40
32
  // [declare]
41
33
 
@@ -62,8 +54,9 @@ const art = function (root) {
62
54
  // [function]
63
55
 
64
56
  const handleGallery = function (entries) {
57
+ const exts = [...Object.keys(cfgimage), ...Object.keys(cfgvideo)];
65
58
  return entries
66
- .filter((f) => extname(f).toLowerCase() === ".png")
59
+ .filter((f) => exts.includes(extname(f).toLowerCase()))
67
60
  .map((f) => join(MEDIA, "gallery", f))
68
61
  .sort();
69
62
  };
@@ -11,10 +11,12 @@ const z = require("zod");
11
11
  const cfgaudience = require("../config/audience");
12
12
  const cfggenre = require("../config/genre");
13
13
  const cfghtml = require("../config/html");
14
+ const cfgimage = require("../config/image");
14
15
  const cfginput = require("../config/input");
15
16
  const cfgorientation = require("../config/orientation");
16
17
  const cfgproduct = require("../config/product");
17
18
  const cfgurl = require("../config/url");
19
+ const cfgvideo = require("../config/video");
18
20
  const cfgvisibility = require("../config/visibility");
19
21
  const check = require("./check/game");
20
22
  const dom = require("../util/dom");
@@ -94,33 +96,48 @@ const zMedia = function (field, root) {
94
96
  // [function]
95
97
 
96
98
  const zPng = function (field, root, opts) {
97
- return zMedia(field, root).refine(
98
- (file) => imageCheck(root, file, { ...opts, type: "image/png" }),
99
- message(field, ["Invalid PNG."]),
100
- );
99
+ return zMedia(field, root).superRefine((file, ctx) => {
100
+ const issues = imageCheck(root, file, { ...opts, type: cfgimage[".png"] });
101
+ if (issues.length) {
102
+ ctx.addIssue({
103
+ code: "custom",
104
+ ...message(field, ["Invalid PNG.", ...issues]),
105
+ });
106
+ }
107
+ });
101
108
  };
102
109
 
103
110
  // [function]
104
111
 
105
112
  const zSvg = function (field, root, opts) {
106
- return zMedia(field, root).refine(
107
- (file) => imageCheck(root, file, { ...opts, type: "image/svg+xml" }),
108
- message(field, ["Invalid SVG."]),
109
- );
113
+ return zMedia(field, root).superRefine((file, ctx) => {
114
+ const issues = imageCheck(root, file, { ...opts, type: cfgimage[".svg"] });
115
+ if (issues.length) {
116
+ ctx.addIssue({
117
+ code: "custom",
118
+ ...message(field, ["Invalid SVG.", ...issues]),
119
+ });
120
+ }
121
+ });
110
122
  };
111
123
 
112
124
  // [function]
113
125
 
114
126
  const zMp4 = function (field, root, opts) {
115
- return zMedia(field, root).refine(
116
- (file) =>
117
- videoCheck(root, file, {
118
- maxDuration: 10,
119
- ...opts,
120
- type: "video/mp4",
121
- }),
122
- message(field, ["Invalid MP4."]),
123
- );
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) {
135
+ ctx.addIssue({
136
+ code: "custom",
137
+ ...message(field, ["Invalid MP4.", ...issues]),
138
+ });
139
+ }
140
+ });
124
141
  };
125
142
 
126
143
  // [function]
@@ -171,7 +188,7 @@ const zBannerVideo = function (root) {
171
188
  return z.optional(
172
189
  zMp4(field, root, {
173
190
  aspect: "2:1",
174
- maxSize: size.mb(5),
191
+ maxSize: size.mb(15),
175
192
  minHeight: 512,
176
193
  minWidth: 1024,
177
194
  }),
@@ -206,7 +223,7 @@ const zCoverVideo = function (root) {
206
223
  return z.optional(
207
224
  zMp4(field, root, {
208
225
  aspect: "3:2",
209
- maxSize: size.mb(5),
226
+ maxSize: size.mb(15),
210
227
  minHeight: 512,
211
228
  minWidth: 768,
212
229
  }),
@@ -241,10 +258,44 @@ const zGallery = function (root) {
241
258
  return z.optional(
242
259
  z
243
260
  .array(
244
- z.union([
245
- zPng(field, root, { maxSize: size.mb(2) }),
246
- zMp4(field, root, { maxSize: size.mb(5) }),
247
- ]),
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
+ }),
248
299
  )
249
300
  .min(1)
250
301
  .max(12),
@@ -293,7 +344,7 @@ const zIconVideo = function (root) {
293
344
  return z.optional(
294
345
  zMp4(field, root, {
295
346
  aspect: "1:1",
296
- maxSize: size.mb(5),
347
+ maxSize: size.mb(15),
297
348
  minHeight: 512,
298
349
  minWidth: 512,
299
350
  }),
@@ -353,7 +404,7 @@ const zPosterVideo = function (root) {
353
404
  return z.optional(
354
405
  zMp4(field, root, {
355
406
  aspect: "2:3",
356
- maxSize: size.mb(5),
407
+ maxSize: size.mb(15),
357
408
  minHeight: 768,
358
409
  minWidth: 512,
359
410
  }),
@@ -437,7 +488,6 @@ const zWordmark = function (root) {
437
488
  const field = "wordmark";
438
489
  return z.optional(
439
490
  zSvg(field, root, {
440
- aspect: "1:1",
441
491
  maxSize: size.kb(512),
442
492
  }),
443
493
  );
@@ -449,7 +499,6 @@ const zWordmarkAnimated = function (root) {
449
499
  const field = "wordmark_animated";
450
500
  return z.optional(
451
501
  zSvg(field, root, {
452
- aspect: "1:1",
453
502
  maxSize: size.mb(1),
454
503
  }),
455
504
  );
@@ -29,7 +29,7 @@ const MEDIA = `.${HANDLE}`;
29
29
  // [function]
30
30
 
31
31
  const reference = function (field) {
32
- return chalk.cyan.underline(`${cfgurl.MANIFEST}#workflow.${field}`);
32
+ return chalk.cyan.underline(`${cfgurl.MANIFEST}#game.${field}`);
33
33
  };
34
34
 
35
35
  // [function]
@@ -64,11 +64,11 @@ const zDeployDir = function (root, { build }) {
64
64
  )
65
65
  .refine(
66
66
  (file) => isOutCheck(root, file),
67
- message(field, ["Must be within the project directory."]),
67
+ message(field, ["Must be within the project."]),
68
68
  )
69
69
  .refine(
70
70
  (file) => isReservedCheck(root, file, MEDIA),
71
- message(field, [`Must not contain ${MEDIA}.`]),
71
+ message(field, [`Must not contain ${MEDIA} in path.`]),
72
72
  )
73
73
  .refine(
74
74
  (file) => build || existsCheck(root, file),
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ // [function]
4
+
5
+ const formatBytes = function (bytes) {
6
+ switch (true) {
7
+ case bytes < 1024:
8
+ return `${bytes} B`;
9
+ case bytes < 1024 ** 2:
10
+ return `${(bytes / 1024).toFixed(1)} KB`;
11
+ case bytes < 1024 ** 3:
12
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
13
+ default:
14
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
15
+ }
16
+ };
17
+
18
+ // [export]
19
+
20
+ module.exports = { formatBytes };
package/src/util/image.js CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  // [import]
4
4
 
5
+ const path = require("node:path");
6
+
7
+ // [import]
8
+
5
9
  const fs = require("fs-extra");
6
10
  const imageSize = require("image-size").imageSize;
7
11
 
12
+ // [import]
13
+
14
+ const bytes = require("./bytes");
15
+ const cfgimage = require("../config/image");
16
+
8
17
  // [use]
9
18
 
19
+ const extname = path.extname;
20
+ const formatBytes = bytes.formatBytes;
10
21
  const readFileSync = fs.readFileSync; // eslint-disable-line no-sync
11
22
  const statSync = fs.statSync; // eslint-disable-line no-sync
12
23
 
@@ -41,42 +52,42 @@ const checkSize = function (file, maxSize) {
41
52
 
42
53
  const checkPng = function (file, { aspect, maxSize, minHeight, minWidth }) {
43
54
  if (!checkSize(file, maxSize)) {
44
- return false;
55
+ return [`Image size must not exceed: ${formatBytes(maxSize)}`];
45
56
  }
46
57
  const buffer = readFileSync(file);
47
58
  const { height, width } = imageSize(buffer);
48
59
  if (minWidth && width < minWidth) {
49
- return false;
60
+ return [`Image width must be at least: ${minWidth}px`];
50
61
  }
51
62
  if (minHeight && height < minHeight) {
52
- return false;
63
+ return [`Image height must be at least: ${minHeight}px`];
53
64
  }
54
65
  if (!checkAspect(width, height, aspect)) {
55
- return false;
66
+ return [`Image aspect ratio must be: ${aspect}`];
56
67
  }
57
- return true;
68
+ return [];
58
69
  };
59
70
 
60
71
  // [function]
61
72
 
62
73
  const checkSvg = function (file, { maxSize }) {
63
74
  if (!checkSize(file, maxSize)) {
64
- return false;
75
+ return [`Image size must not exceed: ${formatBytes(maxSize)}`];
65
76
  }
66
- return true;
77
+ return [];
67
78
  };
68
79
 
69
80
  // [function]
70
81
 
71
82
  const check = function (file, { type, ...opts }) {
72
- switch (type) {
73
- case "image/png":
74
- return checkPng(file, opts);
75
- case "image/svg+xml":
76
- return checkSvg(file, opts);
77
- default:
78
- return false;
79
- }
83
+ const checks = {
84
+ [cfgimage[".png"]]: checkPng,
85
+ [cfgimage[".svg"]]: checkSvg,
86
+ };
87
+ const checkImage = checks[type];
88
+ return checkImage && cfgimage[extname(file)] === type
89
+ ? checkImage(file, opts)
90
+ : ["Image has an unsupported file type."];
80
91
  };
81
92
 
82
93
  // [export]
@@ -4,8 +4,13 @@
4
4
 
5
5
  const cliProgress = require("cli-progress");
6
6
 
7
+ // [import]
8
+
9
+ const bytes = require("./bytes");
10
+
7
11
  // [use]
8
12
 
13
+ const formatBytes = bytes.formatBytes;
9
14
  const Presets = cliProgress.Presets;
10
15
  const SingleBar = cliProgress.SingleBar;
11
16
 
@@ -15,21 +20,6 @@ const FORMAT = "[{bar}] {percentage}% | {value}/{total}";
15
20
 
16
21
  // [function]
17
22
 
18
- const formatBytes = function (bytes) {
19
- switch (true) {
20
- case bytes < 1000:
21
- return `${bytes} B`;
22
- case bytes < 1000 * 1000:
23
- return `${(bytes / 1000).toFixed(1)} KB`;
24
- case bytes < 1000 * 1000 * 1000:
25
- return `${(bytes / 1000 / 1000).toFixed(1)} MB`;
26
- default:
27
- return `${(bytes / 1000 / 1000 / 1000).toFixed(1)} GB`;
28
- }
29
- };
30
-
31
- // [function]
32
-
33
23
  const formatValue = function (value, opts, type) {
34
24
  if (type === "percentage") {
35
25
  return value;
@@ -1,10 +1,34 @@
1
1
  "use strict";
2
2
 
3
+ // [import]
4
+
5
+ const chalk = require("chalk");
6
+
7
+ // [function]
8
+
9
+ const dereference = function (issues) {
10
+ const references = new Set();
11
+ const output = issues.map((issue) =>
12
+ issue
13
+ .split("\n")
14
+ .filter((line) => {
15
+ if (/https?:\/\//u.test(line)) {
16
+ references.add(line.trim());
17
+ return false;
18
+ }
19
+ return true;
20
+ })
21
+ .join("\n"),
22
+ );
23
+ return [output, [...references]];
24
+ };
25
+
3
26
  // [function]
4
27
 
5
28
  const format = function (name, { issues }) {
6
29
  return issues.map(
7
- ({ message, path }) => `${name}.${path.join(".") || "value"}: ${message}`,
30
+ ({ message, path }) =>
31
+ `${chalk.cyan(`${name}.${path.join(".") || "value"}`)}: ${message}`,
8
32
  );
9
33
  };
10
34
 
@@ -22,4 +46,4 @@ const validator = function (name, schema) {
22
46
 
23
47
  // [export]
24
48
 
25
- module.exports = { validator };
49
+ module.exports = { dereference, validator };
package/src/util/video.js CHANGED
@@ -2,15 +2,23 @@
2
2
 
3
3
  // [import]
4
4
 
5
+ const path = require("node:path");
6
+
7
+ // [import]
8
+
5
9
  const createFile = require("mp4box").createFile;
6
10
  const fs = require("fs-extra");
7
11
 
8
12
  // [import]
9
13
 
14
+ const bytes = require("./bytes");
15
+ const cfgvideo = require("../config/video");
10
16
  const phrase = require("../phrase");
11
17
 
12
18
  // [use]
13
19
 
20
+ const extname = path.extname;
21
+ const formatBytes = bytes.formatBytes;
14
22
  const readFileSync = fs.readFileSync; // eslint-disable-line no-sync
15
23
  const statSync = fs.statSync; // eslint-disable-line no-sync
16
24
 
@@ -83,42 +91,46 @@ const readMp4 = function (file) {
83
91
 
84
92
  const checkMp4 = function (
85
93
  file,
86
- { aspect, maxDuration, maxSize, minHeight, minWidth },
94
+ { aspect, maxDuration, maxSize, minDuration, minHeight, minWidth },
87
95
  ) {
88
96
  if (!checkSize(file, maxSize)) {
89
- return false;
97
+ return [`Video size must not exceed: ${formatBytes(maxSize)}`];
90
98
  }
91
99
  let video = null;
92
100
  try {
93
101
  video = readMp4(file);
94
102
  } catch {
95
- return false;
103
+ return ["Video is an invalid file."];
96
104
  }
97
105
  const { duration, height, width } = video;
98
106
  if (minWidth && width < minWidth) {
99
- return false;
107
+ return [`Video width must be at least: ${minWidth}px`];
100
108
  }
101
109
  if (minHeight && height < minHeight) {
102
- return false;
110
+ return [`Video height must be at least: ${minHeight}px`];
111
+ }
112
+ if (minDuration && duration < minDuration) {
113
+ return [`Video duration must be at least: ${minDuration}s`];
103
114
  }
104
115
  if (maxDuration && duration > maxDuration) {
105
- return false;
116
+ return [`Video duration must not exceed: ${maxDuration}s`];
106
117
  }
107
118
  if (!checkAspect(width, height, aspect)) {
108
- return false;
119
+ return [`Video aspect ratio must be: ${aspect}`];
109
120
  }
110
- return true;
121
+ return [];
111
122
  };
112
123
 
113
124
  // [function]
114
125
 
115
126
  const check = function (file, { type, ...opts }) {
116
- switch (type) {
117
- case "video/mp4":
118
- return checkMp4(file, opts);
119
- default:
120
- return false;
121
- }
127
+ const checks = {
128
+ [cfgvideo[".mp4"]]: checkMp4,
129
+ };
130
+ const checkVideo = checks[type];
131
+ return checkVideo && cfgvideo[extname(file)] === type
132
+ ? checkVideo(file, opts)
133
+ : ["Video has an unsupported file type."];
122
134
  };
123
135
 
124
136
  // [export]