arcadible 1.0.2 → 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.
@@ -13,6 +13,7 @@ const CLI = {
13
13
  ARGUMENT_INVALID: "argument_invalid",
14
14
  BUILD_FAILED: "build_failed",
15
15
  DEPLOY_DIR_MISSING: "deploy_dir_missing",
16
+ DEPLOY_RATE_LIMITED: "deploy_rate_limited",
16
17
  GAME_HANDLE_TAKEN: "game_handle_taken",
17
18
  GAME_ID_MISSING: "game_id_missing",
18
19
  GAME_NOT_DEPLOYED: "game_not_deployed",
@@ -43,7 +44,6 @@ const CLI = {
43
44
  TEMPLATE_MISSING: "template_missing",
44
45
  TEMPLATE_UNAVAILABLE: "template_unavailable",
45
46
  UPLOAD_FAILED: "upload_failed",
46
- UPLOAD_RATE_LIMITED: "upload_rate_limited",
47
47
  VALIDATION_FAILED: "validation_failed",
48
48
  WAIT_TIMED_OUT: "wait_timed_out",
49
49
  };
@@ -105,7 +105,7 @@ const WARNING = {
105
105
  DEPLOY_DIR_NOT_BUILT: "deploy_dir_not_built",
106
106
  FIELD_UNKNOWN: "field_unknown",
107
107
  GAME_HANDLE_LAPSED: "game_handle_lapsed",
108
- GAME_HANDLE_LIMIT: "game_handle_limit",
108
+ GAME_HANDLE_LIMIT_REACHED: "game_handle_limit_reached",
109
109
  VERSION_UNCHECKED: "version_unchecked",
110
110
  };
111
111
  // every error code, for checking one exists.
@@ -12,7 +12,7 @@ const strs = require("../util/strs");
12
12
  const deepCompact = objs.deepCompact;
13
13
  const trimWhitespace = strs.trimWhitespace;
14
14
 
15
- // [define]
15
+ // [declare]
16
16
 
17
17
  const COMMENT = 8;
18
18
 
package/src/phrase.js CHANGED
@@ -142,19 +142,19 @@ const didYouMean = function (near) {
142
142
  // [function]
143
143
 
144
144
  const docAttrInvalid = function (tag, attr, value) {
145
- return `Value '${value}' is not valid for the '${attr}' attribute on the '${tag}' tag.`;
145
+ return `Value '${value}' isn't allowed for ${attr} on <${tag}>.`;
146
146
  };
147
147
 
148
148
  // [function]
149
149
 
150
150
  const docAttrUnallowed = function (tag, attr) {
151
- return `Attribute '${attr}' is not permitted on the '${tag}' tag.`;
151
+ return `Attribute ${attr} isn't allowed on <${tag}>.`;
152
152
  };
153
153
 
154
154
  // [function]
155
155
 
156
156
  const docCommentUnallowed = function () {
157
- return "HTML comments are not permitted.";
157
+ return "HTML comments aren't allowed.";
158
158
  };
159
159
 
160
160
  // [function]
@@ -186,7 +186,7 @@ const docMediaType = function (src) {
186
186
  // [function]
187
187
 
188
188
  const docTagUnallowed = function (tag) {
189
- return `Tag '${tag}' is not permitted.`;
189
+ return `Tag <${tag}> isn't allowed.`;
190
190
  };
191
191
 
192
192
  // [function]
@@ -208,13 +208,13 @@ const gameHandleTaken = function (handle) {
208
208
  // [function]
209
209
 
210
210
  const gameHandleLapsed = function (handle) {
211
- return `Handle ${handle} lapsed: it comes back with a production release, so deploying without it.`;
211
+ return `Handle ${handle} lapsed, so deploying without it.\nA production release takes it back.`;
212
212
  };
213
213
 
214
214
  // [function]
215
215
 
216
216
  const gameHandleLimit = function (handle) {
217
- return `Handle ${handle} not applied: your unreleased games already hold 5 handles, so deploying without it.`;
217
+ return `Handle ${handle} not applied, so deploying without it.\nYour unreleased games already hold 5 handles.`;
218
218
  };
219
219
 
220
220
  // [function]
@@ -285,7 +285,7 @@ const packed = function () {
285
285
  // [function]
286
286
 
287
287
  const portInvalid = function () {
288
- return "The port must be a whole number from 1 to 65535.";
288
+ return "`--port` must be a whole number from 1 to 65535.";
289
289
  };
290
290
 
291
291
  // [function]
@@ -311,13 +311,13 @@ const remoteName = function (name) {
311
311
 
312
312
  // arc serve's copy of one of Arcadible's files, kept from `fetchedAt`.
313
313
  const remoteKept = function (name, fetchedAt) {
314
- return `${TITLE} can't be reached, so serving ${remoteName(name)} saved on ${fetchedAt.slice(0, 10)}.`;
314
+ return `Unable to reach ${TITLE}.\nServing ${remoteName(name)} saved on ${fetchedAt.slice(0, 10)}.`;
315
315
  };
316
316
 
317
317
  // [function]
318
318
 
319
319
  const remoteUnreachable = function (name) {
320
- return `${TITLE} can't be reached, and ${remoteName(name)} isn't saved yet. The game runs without it until ${TITLE} is back.`;
320
+ return `Unable to reach ${TITLE}, and ${remoteName(name)} isn't saved.\nThe game runs without it until ${TITLE} is back.`;
321
321
  };
322
322
 
323
323
  // [function]
@@ -334,6 +334,20 @@ const serveAlone = function () {
334
334
 
335
335
  // [function]
336
336
 
337
+ // the label before a play link in a result.
338
+ const playLabel = function () {
339
+ return "Play:";
340
+ };
341
+
342
+ // [function]
343
+
344
+ // the label before a store page's link in a result.
345
+ const storeLabel = function () {
346
+ return "Store:";
347
+ };
348
+
349
+ // [function]
350
+
337
351
  const serveNext = function (dir) {
338
352
  return `Reloads when ${dir}/ changes. Press Ctrl+C to stop.`;
339
353
  };
@@ -359,7 +373,7 @@ const serveNextUrl = function () {
359
373
  // [function]
360
374
 
361
375
  const urlInvalid = function () {
362
- return "The URL must start with http:// or https://, such as http://localhost:5173.";
376
+ return "`--url` must start with http:// or https://.";
363
377
  };
364
378
 
365
379
  // [function]
@@ -368,8 +382,12 @@ const urlInvalid = function () {
368
382
  // says how to add them.
369
383
  const urlNotFrameable = function (url, link) {
370
384
  return blocks(
371
- `⚠ The play page can't frame ${url}.`,
372
- "Send Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Resource-Policy: cross-origin.",
385
+ `The play page can't frame ${url}.`,
386
+ [
387
+ "Its server doesn't send the headers the play page needs:",
388
+ "Cross-Origin-Embedder-Policy: require-corp",
389
+ "Cross-Origin-Resource-Policy: cross-origin",
390
+ ],
373
391
  ["Add them to your server, as the starters' vite.config.js does:", link],
374
392
  );
375
393
  };
@@ -377,7 +395,7 @@ const urlNotFrameable = function (url, link) {
377
395
  // [function]
378
396
 
379
397
  const urlUnreachable = function (url) {
380
- return `⚠ Nothing answers at ${url} yet. Start your server, then reload the play page.`;
398
+ return `Nothing answers at ${url}.\nStart your server, then reload the play page.`;
381
399
  };
382
400
 
383
401
  // [function]
@@ -396,10 +414,11 @@ const releaseFailedHeading = function () {
396
414
 
397
415
  // origin isn't the game's connected repository: none is, or another is.
398
416
  const repoNotConnected = function (game, repo, url) {
399
- return blocks(`${repo} isn't ${game}'s repository on ${TITLE}.`, [
400
- `Connect it in ${TITLE} Studio, on the game's Repository tab (again if it was renamed on GitHub):`,
401
- url,
402
- ]);
417
+ return blocks(
418
+ `${repo} isn't ${game}'s repository on ${TITLE}.`,
419
+ [`Connect it on the game's Repository tab in ${TITLE} Studio:`, url],
420
+ "If it was renamed on GitHub, connect it again.",
421
+ );
403
422
  };
404
423
 
405
424
  // [function]
@@ -411,10 +430,10 @@ const gameNotFound = function (idOrHandle) {
411
430
  // [function]
412
431
 
413
432
  const gameUnavailable = function (id) {
414
- return blocks(`Game unavailable: ${id}`, [
415
- "It was deleted, or belongs to another account.",
433
+ return blocks(
434
+ `Game unavailable: ${id}\nIt was deleted, or belongs to another account.`,
416
435
  "Give game.id a new UUID to deploy it as a new game.",
417
- ]);
436
+ );
418
437
  };
419
438
 
420
439
  // [function]
@@ -430,7 +449,7 @@ const gitUncommitted = function () {
430
449
 
431
450
  const gitRemoteMissing = function () {
432
451
  return blocks(
433
- "No Git remote 'origin'.",
452
+ "No Git remote origin.",
434
453
  "Add the game's GitHub repository as origin.",
435
454
  );
436
455
  };
@@ -439,8 +458,8 @@ const gitRemoteMissing = function () {
439
458
 
440
459
  const gitRemoteUnreachable = function () {
441
460
  return blocks(
442
- "Unable to reach Git remote 'origin'.",
443
- "Check your connection, and that you can access it.",
461
+ "Unable to reach Git remote origin.",
462
+ "Check your internet connection, and that you can access it.",
444
463
  );
445
464
  };
446
465
 
@@ -450,8 +469,14 @@ const manifestMissing = function (file, dir, found) {
450
469
  return blocks(
451
470
  dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}.`,
452
471
  found
453
- ? `The project is in \`${found}\`: run ${TOOL} there, or pass \`-C ${found}\`.`
454
- : `Run ${TOOL} in the project's directory, or pass \`-C\`, or start a project with ${cmdInit()}.`,
472
+ ? [
473
+ `Found a project in ${found}.`,
474
+ `Run \`${TOOL}\` there, or pass \`-C ${found}\`.`,
475
+ ]
476
+ : [
477
+ `Run \`${TOOL}\` in a project's directory, or pass \`-C <dir>\`.`,
478
+ `To start a project, run ${cmdInit()}.`,
479
+ ],
455
480
  );
456
481
  };
457
482
 
@@ -459,7 +484,7 @@ const manifestMissing = function (file, dir, found) {
459
484
 
460
485
  const projectNotRepoRoot = function (prefix) {
461
486
  return blocks(
462
- `${cmdDeployGit()} releases the project at the repository's root, and this one is in ${prefix}.`,
487
+ `Project is in ${prefix}, not the repository's root.\n${cmdDeployGit()} releases only a project at the root.`,
463
488
  `Deploy it with ${cmdDeploy()}, or give it a repository of its own.`,
464
489
  );
465
490
  };
@@ -487,7 +512,7 @@ const gitTagCreateFailed = function (version) {
487
512
  const gitTagPushFailed = function (version) {
488
513
  return blocks(
489
514
  `Unable to push Git tag for v${version}.`,
490
- "Check that you can push to 'origin'.",
515
+ "Check that you can push to origin.",
491
516
  );
492
517
  };
493
518
 
@@ -564,7 +589,7 @@ const imageWidthMin = function (width) {
564
589
  // [function]
565
590
 
566
591
  const timeoutInvalid = function () {
567
- return "The timeout must be a number of seconds above 0.";
592
+ return "`--timeout` must be a number of seconds above 0.";
568
593
  };
569
594
 
570
595
  // [function]
@@ -573,8 +598,8 @@ const timeoutInvalid = function () {
573
598
  // parsed the project again.
574
599
  const issuesAfterBuild = function (count) {
575
600
  return blocks(
576
- `${count} ${count === 1 ? "issue" : "issues"} found after the build.`,
577
- `The build writes files the manifest reads. Run ${cmdValidate()} for details.`,
601
+ `${count} ${count === 1 ? "issue" : "issues"} found after the build.\nThe build writes files the manifest reads.`,
602
+ `Run ${cmdValidate()} for details.`,
578
603
  );
579
604
  };
580
605
 
@@ -590,13 +615,13 @@ const issuesFound = function (count) {
590
615
  // [function]
591
616
 
592
617
  const loginCodeExpired = function () {
593
- return "Login code expired.";
618
+ return blocks("Login code expired.", `Run ${cmdLogin()} again.`);
594
619
  };
595
620
 
596
621
  // [function]
597
622
 
598
623
  const loginFailed = function () {
599
- return "Login request failed.";
624
+ return blocks("Login request failed.", `Run ${cmdLogin()} again.`);
600
625
  };
601
626
 
602
627
  // [function]
@@ -611,10 +636,10 @@ const gameIdMissing = function () {
611
636
  // [function]
612
637
 
613
638
  const lockfileMissing = function () {
614
- return blocks("No lockfile found.", [
615
- "Install your dependencies to create one.",
616
- "Then commit it.",
617
- ]);
639
+ return blocks(
640
+ "No lockfile found.",
641
+ "Install your dependencies to create one, and commit it.",
642
+ );
618
643
  };
619
644
 
620
645
  // [function]
@@ -688,7 +713,7 @@ const packagePathReserved = function (folder) {
688
713
  const packageTooLarge = function (size) {
689
714
  return blocks(
690
715
  `Package is larger than ${size} MB.`,
691
- "Store assets don't count toward it.",
716
+ ".arcadible/ doesn't count toward it.",
692
717
  );
693
718
  };
694
719
 
@@ -712,7 +737,7 @@ const packageUncompressedTooLarge = function (size) {
712
737
  // open it.
713
738
  const releaseFailed = function ({ message }, studio) {
714
739
  return blocks(
715
- (message ?? "").split(/(?<=\.)\s+/u).join("\n"),
740
+ (message ?? "No reason given.").split(/(?<=\.)\s+/u).join("\n"),
716
741
  ...(studio ? [[`${TITLE} Studio:`, studio]] : []),
717
742
  );
718
743
  };
@@ -742,10 +767,10 @@ const releaseTimedOut = function (
742
767
  ]);
743
768
  }
744
769
  if (found) {
745
- return blocks(
746
- head,
747
- `It may still finish. The ${TITLE} check on the tagged commit says how it went.`,
748
- );
770
+ return blocks(head, [
771
+ "It may still finish.",
772
+ `The ${TITLE} check on the tagged commit says how it went.`,
773
+ ]);
749
774
  }
750
775
  return blocks(
751
776
  head,
@@ -753,7 +778,7 @@ const releaseTimedOut = function (
753
778
  "GitHub Actions hasn't released it yet, or its workflow failed:",
754
779
  actions ?? "See the repository's Actions tab.",
755
780
  ],
756
- `If the workflow passed, the ${TITLE} check on the tagged commit says why.`,
781
+ `If the workflow passed, see the ${TITLE} check on the tagged commit.`,
757
782
  );
758
783
  };
759
784
 
@@ -762,7 +787,7 @@ const releaseTimedOut = function (
762
787
  // the spinner while `--wait` waits: on GitHub Actions until the release
763
788
  // exists, then on Arcadible.
764
789
  const releaseWaiting = function (building) {
765
- return building ? "Waiting for GitHub Actions... %s" : "Publishing... %s";
790
+ return building ? "Waiting for GitHub Actions… %s" : "Publishing… %s";
766
791
  };
767
792
 
768
793
  // [function]
@@ -887,9 +912,9 @@ const uploadFailed = function (status) {
887
912
 
888
913
  // [function]
889
914
 
890
- const uploadRateLimited = function (version) {
915
+ const deployRateLimited = function (version) {
891
916
  return blocks(
892
- `v${version} was uploaded in the last 5 minutes.`,
917
+ `v${version} was deployed in the last 5 minutes.`,
893
918
  "Wait, then deploy again.",
894
919
  );
895
920
  };
@@ -1022,6 +1047,7 @@ module.exports = {
1022
1047
  cmdInit,
1023
1048
  cmdInspect,
1024
1049
  deployDirMissing,
1050
+ deployRateLimited,
1025
1051
  didYouMean,
1026
1052
  docAttrInvalid,
1027
1053
  docAttrUnallowed,
@@ -1083,6 +1109,7 @@ module.exports = {
1083
1109
  packageTooManyFiles,
1084
1110
  packageUncompressedTooLarge,
1085
1111
  packed,
1112
+ playLabel,
1086
1113
  portInUse,
1087
1114
  portInvalid,
1088
1115
  pressEnterToOpen,
@@ -1110,6 +1137,7 @@ module.exports = {
1110
1137
  starterPhaser,
1111
1138
  starterPixiJs,
1112
1139
  starterThreeJs,
1140
+ storeLabel,
1113
1141
  tagPushed,
1114
1142
  templateMissing,
1115
1143
  templateUnavailable,
@@ -1121,7 +1149,6 @@ module.exports = {
1121
1149
  unknownField,
1122
1150
  unknownTable,
1123
1151
  uploadFailed,
1124
- uploadRateLimited,
1125
1152
  urlInvalid,
1126
1153
  urlNotFrameable,
1127
1154
  urlUnreachable,
@@ -123,16 +123,20 @@ const GALLERY_DIMENSIONS = {
123
123
  // leaving a field out is how to unset it, so an empty one is always a mistake.
124
124
  const atLeast = function (field, min, unit) {
125
125
  return min === 1
126
- ? message(field, ["Must not be empty. Leave the field out instead."])
127
- : message(field, [`Must be at least: ${min} ${unit}`]);
126
+ ? message(field, ["Must not be empty.", "Leave the field out instead."])
127
+ : message(field, [
128
+ unit === "character"
129
+ ? `Must be at least ${min} characters.`
130
+ : `Must have at least ${min} ${unit}s.`,
131
+ ]);
128
132
  };
129
133
 
130
134
  // [function]
131
135
 
132
- // a required string's error: "Must be defined." and `lines` when it's missing,
136
+ // a required string's error: "Must be set." and `lines` when it's missing,
133
137
  // and zod's own type message (null) when it's there but isn't a string.
134
138
  const required = function (field, lines) {
135
- const missing = message(field, ["Must be defined.", ...lines]).message;
139
+ const missing = message(field, ["Must be set.", ...lines]).message;
136
140
  return {
137
141
  error(issue) {
138
142
  return typeof issue.input === "undefined" ? missing : null;
@@ -144,14 +148,14 @@ const required = function (field, lines) {
144
148
 
145
149
  // a list of distinct values. Without a max, its length is up to the values.
146
150
  const zSet = function (field, schema, { max, min }) {
147
- const list = z.array(schema).min(min, atLeast(field, min, "element"));
151
+ const list = z.array(schema).min(min, atLeast(field, min, "item"));
148
152
  return (
149
153
  max
150
- ? list.max(max, message(field, [`Must not exceed: ${max} elements`]))
154
+ ? list.max(max, message(field, [`Must have at most ${max} items.`]))
151
155
  : list
152
156
  ).refine(
153
157
  (items) => new Set(items).size === items.length,
154
- message(field, ["Must not contain duplicate elements."], ISSUE.DUPLICATE),
158
+ message(field, ["Must not repeat an item."], ISSUE.DUPLICATE),
155
159
  );
156
160
  };
157
161
 
@@ -161,12 +165,12 @@ const zSetBasefile = function (field, schema, { max, min }) {
161
165
  return z
162
166
  .array(schema)
163
167
  .min(min, atLeast(field, min, "file"))
164
- .max(max, message(field, [`Must not exceed: ${max} files`]))
168
+ .max(max, message(field, [`Must have at most ${max} files.`]))
165
169
  .refine(
166
170
  (items) => new Set(items.map(basefile)).size === items.length,
167
171
  message(
168
172
  field,
169
- ["Must not contain duplicate filenames (ignoring extensions)."],
173
+ ["Must not have two files of one name, whatever their extensions."],
170
174
  ISSUE.DUPLICATE,
171
175
  ),
172
176
  );
@@ -183,7 +187,7 @@ const zString = function (field, char, { max, min }) {
183
187
  .min(min, { ...atLeast(field, min, "character"), abort: min === 1 })
184
188
  .refine(
185
189
  (str) => [...str].length <= max,
186
- message(field, [`Must not exceed: ${max} characters`], ISSUE.TOO_LONG),
190
+ message(field, [`Must be at most ${max} characters.`], ISSUE.TOO_LONG),
187
191
  )
188
192
  .superRefine((str, ctx) => {
189
193
  const invalid = invalidChars(str, char);
@@ -458,7 +462,7 @@ const zHandle = function () {
458
462
  const field = "handle";
459
463
  return z.optional(
460
464
  zString(field, /[a-z0-9-]/u, { max: 24, min: 1 })
461
- .min(3, atLeast(field, 3, "characters"))
465
+ .min(3, atLeast(field, 3, "character"))
462
466
  .refine(
463
467
  (str) => !str.startsWith("-") && !str.endsWith("-"),
464
468
  message(
@@ -487,7 +491,7 @@ const zId = function () {
487
491
  required(field, [`Run ${phrase.cmdInit()} to generate a new game ID.`]),
488
492
  )
489
493
  .min(1, { ...atLeast(field, 1), abort: true })
490
- .uuid(message(field, ["Must be a valid UUID."]))
494
+ .uuid(message(field, ["Must be a UUID."]))
491
495
  .refine(
492
496
  (value) => value === value.toLowerCase(),
493
497
  message(field, ["Must be lowercase."], ISSUE.UUID_INVALID),
@@ -514,8 +518,8 @@ const zPrice = function () {
514
518
  return z.optional(
515
519
  z
516
520
  .int()
517
- .min(0, message(field, ["Must not be below: 0"]))
518
- .max(25, message(field, ["Must not be above: 25"])),
521
+ .min(0, message(field, ["Must be 0 or more."]))
522
+ .max(25, message(field, ["Must be 25 or less."])),
519
523
  );
520
524
  };
521
525
 
@@ -547,7 +551,7 @@ const zTrailer = function () {
547
551
  return z
548
552
  .string()
549
553
  .min(1, { ...atLeast(field, 1), abort: true })
550
- .max(255, message(field, ["Must not exceed: 255 characters"]))
554
+ .max(255, message(field, ["Must be at most 255 characters."]))
551
555
  .refine(
552
556
  (url) =>
553
557
  zVimeo().safeParse(url).success || zYouTube().safeParse(url).success,
@@ -561,18 +565,26 @@ const zTrailer = function () {
561
565
  const zVersion = function () {
562
566
  const field = "version";
563
567
  return z
564
- .string(
565
- required(field, ["A version is required unless one can be derived."]),
566
- )
568
+ .string(required(field, ["Set it here, or in package.json."]))
567
569
  .min(1, { ...atLeast(field, 1), abort: true })
568
570
  .refine((value) => validSemver(value) !== null, {
569
- ...message(field, ["Must be valid semver."], ISSUE.VERSION_INVALID),
571
+ ...message(
572
+ field,
573
+ ["Must be a version, such as 1.2.0."],
574
+ ISSUE.VERSION_INVALID,
575
+ ),
570
576
  abort: true,
571
577
  })
572
578
  .refine(
573
579
  // lowercase too, since the database refuses an uppercase prerelease.
574
580
  (value) => cleanSemver(value) === value && value === value.toLowerCase(),
575
- message(field, ["Must be normalized semver."], ISSUE.VERSION_INVALID),
581
+ message(
582
+ field,
583
+ [
584
+ "Must be written as 1.2.0 or 1.2.0-beta.1: lowercase, with no v, spaces, or build metadata.",
585
+ ],
586
+ ISSUE.VERSION_INVALID,
587
+ ),
576
588
  );
577
589
  };
578
590
 
@@ -29,7 +29,8 @@ const main = function (value, { file }) {
29
29
  ...message(
30
30
  "description",
31
31
  [
32
- `${file} has content after its title, tagline, and bio, so it needs two --- rules around the description.`,
32
+ "Needs two --- rules around the description.",
33
+ `${file} has content after its title, tagline, and bio.`,
33
34
  ],
34
35
  cfgcode.ISSUE.DESCRIPTION_RULES_MISSING,
35
36
  ),
@@ -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]