arcadible 1.0.1 → 1.0.4

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.
@@ -68,7 +68,7 @@ const zDeployDir = function (root, { build }) {
68
68
  return z
69
69
  .string()
70
70
  .min(1, {
71
- ...message(field, ["Must not be empty. Leave the field out instead."]),
71
+ ...message(field, ["Must not be empty.", "Leave the field out instead."]),
72
72
  abort: true,
73
73
  })
74
74
  .refine((file) => isRelativeCheck(root, file), {
@@ -82,24 +82,20 @@ const zDeployDir = function (root, { build }) {
82
82
  .refine((file) => !isRootCheck(root, file), {
83
83
  ...message(
84
84
  field,
85
- ["Must be a directory inside the project, not the project root."],
85
+ ["Must be a folder in the project, not its root."],
86
86
  ISSUE.PATH_INVALID,
87
87
  ),
88
88
  abort: true,
89
89
  })
90
90
  .refine((file) => avoidsCheck(root, file, MEDIA), {
91
- ...message(
92
- field,
93
- [`Must not contain ${MEDIA} in path.`],
94
- ISSUE.PATH_INVALID,
95
- ),
91
+ ...message(field, [`Must not be in ${MEDIA}.`], ISSUE.PATH_INVALID),
96
92
  abort: true,
97
93
  })
98
94
  .refine((file) => hasBuild(build) || existsCheck(root, file), {
99
95
  ...message(
100
96
  field,
101
97
  [
102
- "Must exist when no build step is defined.",
98
+ "Must exist, since there's no build.",
103
99
  `Run ${phrase.cmdInspect()} for details.`,
104
100
  ],
105
101
  ISSUE.PATH_INVALID,
@@ -118,7 +114,8 @@ const zDeployDir = function (root, { build }) {
118
114
  ...message(
119
115
  field,
120
116
  [
121
- `Must not have ${RESERVED.join(" or ")} at its root. They're reserved for the files ${cfgproduct.title} adds.`,
117
+ `Must not have ${RESERVED.join(" or ")} at its root.`,
118
+ `They're reserved for the files ${cfgproduct.title} adds.`,
122
119
  ],
123
120
  ISSUE.PATH_INVALID,
124
121
  ),
@@ -128,7 +125,8 @@ const zDeployDir = function (root, { build }) {
128
125
  ...message(
129
126
  field,
130
127
  [
131
- `Must not have an ${RESERVED_FOLDER} folder at its root, in any letter case. It's reserved for ${cfgproduct.title}'s own files.`,
128
+ `Must not have an ${RESERVED_FOLDER} folder at its root, in any letter case.`,
129
+ `It's reserved for ${cfgproduct.title}'s own files.`,
132
130
  ],
133
131
  ISSUE.PATH_INVALID,
134
132
  ),
package/src/serve/game.js CHANGED
@@ -98,7 +98,13 @@ const sendFile = function (req, res, file, headers) {
98
98
  res.end();
99
99
  return;
100
100
  }
101
- file.open(range).pipe(res);
101
+ // the file can go between finding and reading it, as a rebuild empties
102
+ // deploy_dir: the headers are sent, so the response just ends
103
+ const stream = file.open(range);
104
+ stream.on("error", () => {
105
+ res.destroy();
106
+ });
107
+ stream.pipe(res);
102
108
  };
103
109
 
104
110
  // [function]
package/src/serve/page.js CHANGED
@@ -24,6 +24,17 @@ const SANDBOX =
24
24
  const HOST = "/_arcadible/host.js";
25
25
  const RELOAD = "/_arcadible/reload";
26
26
 
27
+ // [declare]
28
+
29
+ // the headers of the page's responses, as the play page's: it's
30
+ // cross-origin isolated, and nothing frames it.
31
+ const HEADERS = {
32
+ "cache-control": "no-store",
33
+ "content-security-policy": "frame-ancestors 'none';",
34
+ "cross-origin-embedder-policy": "require-corp",
35
+ "cross-origin-opener-policy": "same-origin",
36
+ };
37
+
27
38
  // [function]
28
39
 
29
40
  // `value` as JSON that's safe inside a <script>.
@@ -105,17 +116,6 @@ const html = function (game) {
105
116
 
106
117
  // [function]
107
118
 
108
- // the headers of the page's responses, as the play page's: it's
109
- // cross-origin isolated, and nothing frames it.
110
- const HEADERS = {
111
- "cache-control": "no-store",
112
- "content-security-policy": "frame-ancestors 'none';",
113
- "cross-origin-embedder-policy": "require-corp",
114
- "cross-origin-opener-policy": "same-origin",
115
- };
116
-
117
- // [function]
118
-
119
119
  // the request handler for the page's origin. `reloads` keeps the pages
120
120
  // listening for a reload; `remote` has the host.
121
121
  const pageHandler = function ({
@@ -52,6 +52,11 @@ const watchDir = function (dir, onChange) {
52
52
  clearTimeout(timer);
53
53
  timer = setTimeout(onChange, SETTLE);
54
54
  });
55
+ // deploy_dir removed while it's watched (EPERM on Windows): stop watching
56
+ // rather than crash the server
57
+ watcher.on("error", () => {
58
+ watcher.close();
59
+ });
55
60
  return () => {
56
61
  clearTimeout(timer);
57
62
  watcher.close();
package/src/util/log.js CHANGED
@@ -30,6 +30,13 @@ const log = function (out, message, { bold = false, color, nl = false } = {}) {
30
30
 
31
31
  // [function]
32
32
 
33
+ // a warning, marked with ⚠ (OUTPUT.md), on its first line.
34
+ const warned = function (message) {
35
+ return `⚠ ${[message].flat().join("\n").trim()}`;
36
+ };
37
+
38
+ // [function]
39
+
33
40
  const h = function (message) {
34
41
  log(console.log, message, { bold: true });
35
42
  };
@@ -91,13 +98,13 @@ const hwo = function (message) {
91
98
  // [function]
92
99
 
93
100
  const pw = function (message) {
94
- log(console.warn, message, { color: "yellow" });
101
+ log(console.warn, warned(message), { color: "yellow" });
95
102
  };
96
103
 
97
104
  // [function]
98
105
 
99
106
  const nlpw = function (message) {
100
- log(console.warn, message, { color: "yellow", nl: true });
107
+ log(console.warn, warned(message), { color: "yellow", nl: true });
101
108
  };
102
109
 
103
110
  // [function]
package/src/util/md.js CHANGED
@@ -32,7 +32,7 @@ const escape = function (str) {
32
32
  .replace(/>/gu, "&gt;");
33
33
  };
34
34
 
35
- // [declare]
35
+ // [define]
36
36
 
37
37
  // there is no video syntax in Markdown, so an image of a video renders as the
38
38
  // video the developer would have written: its src and title, and no alt.
@@ -59,6 +59,20 @@ const print = function (data, { key, nl, toml } = {}) {
59
59
 
60
60
  // [function]
61
61
 
62
+ // exits with `code` once stdout has written what it holds. A pipe on macOS
63
+ // writes later, so exiting at once would cut a large --json document short;
64
+ // with nothing held, it exits at once, as a caller that goes on expects.
65
+ const exit = function (code) {
66
+ if (!process.stdout.writableLength) {
67
+ process.exit(code);
68
+ }
69
+ process.exitCode = code;
70
+ process.stdout.write("", () => process.exit(code));
71
+ return new Promise(() => {});
72
+ };
73
+
74
+ // [function]
75
+
62
76
  // a command's error path: the error as JSON, or its heading ("Deploy
63
77
  // aborted.") and the reason on stderr, then its code and the link to it on
64
78
  // the Errors page, once the spinner and progress bar are cleared. Then exits
@@ -102,7 +116,7 @@ const abort = function (
102
116
  log.pd(message);
103
117
  log.nlpd([phrase.errorCode(shown), chalk.cyan.underline(link)]);
104
118
  }
105
- process.exit(1);
119
+ return exit(1);
106
120
  };
107
121
 
108
122
  // [function]
@@ -128,7 +142,7 @@ const warn = function (warnings, { json, quiet } = {}) {
128
142
  if (json || quiet) {
129
143
  return;
130
144
  }
131
- warnings.forEach((each) => log.pw(`⚠ ${schema.print(each)}`));
145
+ warnings.forEach((each) => log.pw(schema.print(each)));
132
146
  };
133
147
 
134
148
  // [function]
@@ -150,6 +164,7 @@ const warning = function (code, message, path = null) {
150
164
 
151
165
  module.exports = {
152
166
  abort,
167
+ exit,
153
168
  launched,
154
169
  print,
155
170
  printJson,
@@ -17,6 +17,14 @@ const ISSUE = cfgcode.ISSUE;
17
17
 
18
18
  // [declare]
19
19
 
20
+ // how a value's kind is named, as TOML writes it.
21
+ const KINDS = {
22
+ array: "a list",
23
+ boolean: "true or false",
24
+ number: "a number",
25
+ object: "a table",
26
+ string: "a string",
27
+ };
20
28
  const NUMBERS = new Set(["bigint", "int", "number"]);
21
29
 
22
30
  // [function]
@@ -65,11 +73,54 @@ const issueCodeOf = function (issue) {
65
73
 
66
74
  // [function]
67
75
 
76
+ // a value's kind, as TOML writes it: "a string", "a list", "a table".
77
+ const kindOf = function (type) {
78
+ return KINDS[type] ?? `a ${type}`;
79
+ };
80
+
81
+ // [function]
82
+
83
+ // what zod reports itself (a value of the wrong type, or not one of the
84
+ // options), in arc's words, as `{ message, details }`: "Must be a string, not
85
+ // a number." Null for a message of our own.
86
+ const zodMessage = function (issue) {
87
+ if (issue.code === "invalid_value" && issue.values) {
88
+ return { details: [issue.values.join(", ")], message: "Must be one of:" };
89
+ }
90
+ const given = Array.isArray(issue.input) ? "array" : typeof issue.input;
91
+ if (issue.code === "invalid_type" && typeof issue.input !== "undefined") {
92
+ return {
93
+ details: [],
94
+ message: `Must be ${kindOf(issue.expected)}, not ${kindOf(given)}.`,
95
+ };
96
+ }
97
+ if (
98
+ issue.code === "invalid_union" &&
99
+ issue.errors.every((branch) =>
100
+ branch.every((each) => each.code === "invalid_type"),
101
+ )
102
+ ) {
103
+ const expected = [
104
+ ...new Set(issue.errors.map((branch) => kindOf(branch[0].expected))),
105
+ ];
106
+ return {
107
+ details: [],
108
+ message: `Must be ${expected.join(" or ")}, not ${kindOf(given)}.`,
109
+ };
110
+ }
111
+ return null;
112
+ };
113
+
114
+ // [function]
115
+
68
116
  // a zod issue as arc validate reports it: the field's path, the rule's code,
69
117
  // the message's first line and its details, and the link to the field's
70
118
  // rules, or null for a field the docs leave out.
71
119
  const toIssue = function (name, issue) {
72
- const { details, message } = schemaMessage.parse(issue.message);
120
+ const parsed = schemaMessage.parse(issue.message);
121
+ const { details, message } =
122
+ (/^Invalid (input|option)\b/u.test(parsed.message) && zodMessage(issue)) ||
123
+ parsed;
73
124
  return {
74
125
  code: issueCodeOf(issue),
75
126
  details,
@@ -133,5 +184,6 @@ module.exports = {
133
184
  dereference,
134
185
  issueCodeOf,
135
186
  print,
187
+ toIssue,
136
188
  validator,
137
189
  };