arcadible 0.1.44 → 0.1.46

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.
Files changed (55) hide show
  1. package/bin/arc-build.js +9 -17
  2. package/bin/arc-deploy.js +20 -36
  3. package/bin/arc-init.js +6 -16
  4. package/bin/arc-inspect.js +3 -7
  5. package/bin/arc-login.js +5 -5
  6. package/bin/arc-logout.js +4 -8
  7. package/bin/arc-open.js +7 -20
  8. package/bin/arc-pack.js +4 -14
  9. package/bin/arc-play.js +6 -20
  10. package/bin/arc-validate.js +8 -17
  11. package/bin/arc-whoami.js +5 -9
  12. package/bin/arc.js +7 -2
  13. package/package.json +1 -1
  14. package/src/account.js +5 -5
  15. package/src/action/build/tool.js +8 -15
  16. package/src/action/deploy/tool-git.js +1 -6
  17. package/src/action/deploy/wait.js +5 -11
  18. package/src/action/init/manifest.js +9 -37
  19. package/src/action/login/flow.js +3 -10
  20. package/src/action/open/tool.js +4 -3
  21. package/src/action/pack/limits.js +4 -6
  22. package/src/action/pack/tool.js +9 -5
  23. package/src/action/play/tool.js +6 -6
  24. package/src/action/predeploy/ac.js +34 -31
  25. package/src/action/predeploy/git.js +4 -4
  26. package/src/action/predeploy/pm.js +1 -1
  27. package/src/action/predeploy/published.js +4 -4
  28. package/src/action/predeploy/tool-git.js +10 -10
  29. package/src/action/validate/unknown.js +11 -10
  30. package/src/config/code.js +1 -1
  31. package/src/config/file.js +23 -0
  32. package/src/config/limit.js +21 -0
  33. package/src/parser/env.js +0 -2
  34. package/src/parser/manifest.js +15 -13
  35. package/src/parser/media.js +9 -11
  36. package/src/parser/project.js +31 -9
  37. package/src/parser/self.js +27 -0
  38. package/src/phrase.js +175 -43
  39. package/src/schema/game.js +65 -42
  40. package/src/schema/workflow.js +7 -8
  41. package/src/util/argv.js +27 -1
  42. package/src/util/command.js +18 -0
  43. package/src/util/delay.js +5 -21
  44. package/src/util/doc.js +4 -13
  45. package/src/util/links.js +17 -2
  46. package/src/util/media.js +2 -2
  47. package/src/util/objs.js +8 -1
  48. package/src/util/output.js +24 -2
  49. package/src/util/rc.js +2 -2
  50. package/src/util/request.js +1 -1
  51. package/src/util/s3.js +1 -1
  52. package/src/util/schema.js +9 -3
  53. package/src/util/uris.js +1 -1
  54. package/src/util/zip.js +2 -2
  55. package/src/config/doc.js +0 -7
package/src/phrase.js CHANGED
@@ -32,6 +32,13 @@ const waited = function (seconds) {
32
32
 
33
33
  // [function]
34
34
 
35
+ // a command's heading when it fails: `Deploy aborted.`
36
+ const aborted = function (command) {
37
+ return `${command[0].toUpperCase()}${command.slice(1)} aborted.`;
38
+ };
39
+
40
+ // [function]
41
+
35
42
  // a build that exited with `code`, or was stopped by `signal`, with what it
36
43
  // wrote to stderr.
37
44
  const buildFailed = function ({ code, signal, stderr }) {
@@ -71,12 +78,30 @@ const buildSubmitted = function (version, channel, built) {
71
78
 
72
79
  // [function]
73
80
 
81
+ const buildSucceeded = function () {
82
+ return "Built.";
83
+ };
84
+
85
+ // [function]
86
+
87
+ const cmdBuild = function () {
88
+ return `\`${TOOL} build\``;
89
+ };
90
+
91
+ // [function]
92
+
74
93
  const cmdDeploy = function () {
75
94
  return `\`${TOOL} deploy\``;
76
95
  };
77
96
 
78
97
  // [function]
79
98
 
99
+ const cmdDeployGit = function () {
100
+ return `\`${TOOL} deploy git\``;
101
+ };
102
+
103
+ // [function]
104
+
80
105
  const cmdInit = function () {
81
106
  return `\`${TOOL} init\``;
82
107
  };
@@ -104,12 +129,18 @@ const cmdValidate = function () {
104
129
  const deployDirMissing = function (dir) {
105
130
  return blocks(
106
131
  `Unable to find deploy_dir: ${dir}`,
107
- "Run `arc build`, or set deploy_dir to the folder your build writes.",
132
+ `Run ${cmdBuild()}, or set deploy_dir to the folder your build writes.`,
108
133
  );
109
134
  };
110
135
 
111
136
  // [function]
112
137
 
138
+ const didYouMean = function (near) {
139
+ return `Did you mean: ${near}?`;
140
+ };
141
+
142
+ // [function]
143
+
113
144
  const docAttrInvalid = function (tag, attr, value) {
114
145
  return `Value '${value}' is not valid for the '${attr}' attribute on the '${tag}' tag.`;
115
146
  };
@@ -140,20 +171,22 @@ const docMediaMissing = function (src) {
140
171
 
141
172
  // [function]
142
173
 
143
- const docMediaType = function (src) {
144
- return `Must be a PNG or MP4 file: ${src}`;
174
+ // a description media src that isn't written as the gallery's path to it,
175
+ // such as `./.arcadible/gallery/x.png`: Arcadible matches it as written.
176
+ const docMediaPath = function (src, gallery) {
177
+ return `Must be written as ${gallery}/<file>: ${src}`;
145
178
  };
146
179
 
147
180
  // [function]
148
181
 
149
- const docTagUnallowed = function (tag) {
150
- return `Tag '${tag}' is not permitted.`;
182
+ const docMediaType = function (src) {
183
+ return `Must be a PNG or MP4 file: ${src}`;
151
184
  };
152
185
 
153
186
  // [function]
154
187
 
155
- const docUnsupported = function () {
156
- return "Document has an unsupported file type.";
188
+ const docTagUnallowed = function (tag) {
189
+ return `Tag '${tag}' is not permitted.`;
157
190
  };
158
191
 
159
192
  // [function]
@@ -165,8 +198,11 @@ const errorCode = function (code) {
165
198
 
166
199
  // [function]
167
200
 
168
- const gameExists = function (idOrHandle) {
169
- return `Game exists: ${idOrHandle}`;
201
+ const gameHandleTaken = function (handle) {
202
+ return blocks(
203
+ `Handle taken: ${handle}`,
204
+ "Choose another handle in arcadible.toml.",
205
+ );
170
206
  };
171
207
 
172
208
  // [function]
@@ -180,8 +216,76 @@ const gameNotDeployed = function (game) {
180
216
 
181
217
  // [function]
182
218
 
219
+ const initialized = function () {
220
+ return "Initialized.";
221
+ };
222
+
223
+ // [function]
224
+
225
+ // the heading of arc validate's list of issues, warnings, or references:
226
+ // `2 issues:`
227
+ const listed = function (count, noun) {
228
+ return `${count} ${noun}${count === 1 ? "" : "s"}:`;
229
+ };
230
+
231
+ // [function]
232
+
233
+ const loggedIn = function () {
234
+ return "Logged in.";
235
+ };
236
+
237
+ // [function]
238
+
239
+ const loggedOut = function () {
240
+ return "Logged out.";
241
+ };
242
+
243
+ // [function]
244
+
245
+ const loggingIn = function () {
246
+ return "Logging in...";
247
+ };
248
+
249
+ // [function]
250
+
251
+ const noAccount = function () {
252
+ return "Not logged in.";
253
+ };
254
+
255
+ // [function]
256
+
257
+ const noIssuesFound = function () {
258
+ return "No issues found.";
259
+ };
260
+
261
+ // [function]
262
+
263
+ const nothingToDo = function () {
264
+ return "Nothing to do.";
265
+ };
266
+
267
+ // [function]
268
+
269
+ const packed = function () {
270
+ return "Packed.";
271
+ };
272
+
273
+ // [function]
274
+
275
+ const pressEnterToOpen = function () {
276
+ return "Press Enter to open the URL.";
277
+ };
278
+
279
+ // [function]
280
+
281
+ const releaseFailedHeading = function () {
282
+ return "Release failed.";
283
+ };
284
+
285
+ // [function]
286
+
183
287
  // origin isn't the game's connected repository: none is, or another is.
184
- const gameRepoMissing = function (game, repo, url) {
288
+ const repoNotConnected = function (game, repo, url) {
185
289
  return blocks(`${repo} isn't ${game}'s repository on ${TITLE}.`, [
186
290
  `The game's owner connects it in ${TITLE} Studio, on the game's Repository tab, and connects it again if it was renamed on GitHub:`,
187
291
  url,
@@ -205,7 +309,7 @@ const gameUnavailable = function (id) {
205
309
 
206
310
  // [function]
207
311
 
208
- const gitChangesPresent = function () {
312
+ const gitUncommitted = function () {
209
313
  return blocks(
210
314
  "Uncommitted changes found.",
211
315
  "Commit, stash, or discard them first.",
@@ -232,12 +336,12 @@ const gitRemoteUnreachable = function () {
232
336
 
233
337
  // [function]
234
338
 
235
- const noManifest = function (file, dir, found) {
339
+ const manifestMissing = function (file, dir, found) {
236
340
  return blocks(
237
341
  dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}.`,
238
342
  found
239
- ? `The project is in \`${found}\`: run arc there, or pass \`-C ${found}\`.`
240
- : "Run arc in the project's directory, or pass -C, or start a project with `arc init`.",
343
+ ? `The project is in \`${found}\`: run ${TOOL} there, or pass \`-C ${found}\`.`
344
+ : `Run ${TOOL} in the project's directory, or pass \`-C\`, or start a project with ${cmdInit()}.`,
241
345
  );
242
346
  };
243
347
 
@@ -245,8 +349,8 @@ const noManifest = function (file, dir, found) {
245
349
 
246
350
  const projectNotRepoRoot = function (prefix) {
247
351
  return blocks(
248
- `arc deploy git releases the project at the repository's root, and this one is in ${prefix}`,
249
- "Deploy it with `arc deploy`, or give it a repository of its own.",
352
+ `${cmdDeployGit()} releases the project at the repository's root, and this one is in ${prefix}.`,
353
+ `Deploy it with ${cmdDeploy()}, or give it a repository of its own.`,
250
354
  );
251
355
  };
252
356
 
@@ -279,7 +383,7 @@ const gitTagPushFailed = function (version) {
279
383
 
280
384
  // [function]
281
385
 
282
- const gitVersionExists = function (version) {
386
+ const gitTagExists = function (version) {
283
387
  return blocks(
284
388
  `Git tag for v${version} already exists, here or on origin.`,
285
389
  "Bump the version.",
@@ -387,16 +491,16 @@ const loginFailed = function () {
387
491
 
388
492
  // [function]
389
493
 
390
- const noGameId = function () {
494
+ const gameIdMissing = function () {
391
495
  return blocks(
392
496
  "No game handle or ID.",
393
- `Pass one, or run ${cmdInit()} to add an ID.`,
497
+ `Pass one, or run it in a project after ${cmdInit()} has added an ID.`,
394
498
  );
395
499
  };
396
500
 
397
501
  // [function]
398
502
 
399
- const noLockFile = function () {
503
+ const lockfileMissing = function () {
400
504
  return blocks("No lockfile found.", [
401
505
  "Install your dependencies to create one.",
402
506
  "Then commit it.",
@@ -411,7 +515,7 @@ const noProductionRelease = function () {
411
515
 
412
516
  // [function]
413
517
 
414
- const noTemplate = function () {
518
+ const templateMissing = function () {
415
519
  return blocks("No template chosen.", [
416
520
  "Pass `--template <user/repo>`.",
417
521
  "Or pass `--yes` for the blank starter.",
@@ -421,7 +525,7 @@ const noTemplate = function () {
421
525
  // [function]
422
526
 
423
527
  const notLoggedIn = function () {
424
- return blocks("Not logged in.", `Run ${cmdLogin()} to log in.`);
528
+ return blocks(noAccount(), `Run ${cmdLogin()} to log in.`);
425
529
  };
426
530
 
427
531
  // [function]
@@ -450,7 +554,7 @@ const openToContinue = function () {
450
554
 
451
555
  // [function]
452
556
 
453
- const packageFileMissing = function (file) {
557
+ const packageIndexMissing = function (file) {
454
558
  return `Package is missing '${file}'.`;
455
559
  };
456
560
 
@@ -619,12 +723,6 @@ const templateUnavailable = function (template) {
619
723
 
620
724
  // [function]
621
725
 
622
- const timedOut = function () {
623
- return "Timed out.";
624
- };
625
-
626
- // [function]
627
-
628
726
  const unableToFind = function (file) {
629
727
  return `Unable to find: ${file}`;
630
728
  };
@@ -638,7 +736,7 @@ const unableToOpen = function () {
638
736
  // [function]
639
737
 
640
738
  const unableToParse = function (file, message) {
641
- return blocks(`Unable to parse ${file}.`, message);
739
+ return blocks(`Unable to parse ${file}.`, message.trim());
642
740
  };
643
741
 
644
742
  // [function]
@@ -652,6 +750,18 @@ const unableToReach = function () {
652
750
 
653
751
  // [function]
654
752
 
753
+ const unknownField = function () {
754
+ return "Unknown field.";
755
+ };
756
+
757
+ // [function]
758
+
759
+ const unknownTable = function () {
760
+ return "Unknown table.";
761
+ };
762
+
763
+ // [function]
764
+
655
765
  const uploadFailed = function (status) {
656
766
  return `Upload failed with status ${status}.`;
657
767
  };
@@ -667,6 +777,12 @@ const uploadRateLimited = function (version) {
667
777
 
668
778
  // [function]
669
779
 
780
+ const waitTimedOut = function () {
781
+ return "Wait timed out.";
782
+ };
783
+
784
+ // [function]
785
+
670
786
  const workflowMissing = function (file) {
671
787
  return blocks(
672
788
  `No release workflow found: ${file}`,
@@ -684,7 +800,7 @@ const versionPrerelease = function (version) {
684
800
 
685
801
  // a version that's already published, with what is, so the developer knows
686
802
  // what to bump to.
687
- const versionPublished = function (version, { next, production, staging }) {
803
+ const versionExists = function (version, { next, production, staging }) {
688
804
  const latest = [
689
805
  production ? `Production: ${production}` : null,
690
806
  staging ? `Staging: ${staging}` : null,
@@ -777,35 +893,38 @@ const videoWidthMin = function (width) {
777
893
  // [export]
778
894
 
779
895
  module.exports = {
896
+ aborted,
780
897
  buildFailed,
781
898
  buildOutputMissing,
782
899
  buildPublishing,
783
900
  buildPublishingGit,
784
901
  buildSubmitted,
902
+ buildSucceeded,
785
903
  cmdInit,
786
904
  cmdInspect,
787
905
  deployDirMissing,
906
+ didYouMean,
788
907
  docAttrInvalid,
789
908
  docAttrUnallowed,
790
909
  docCommentUnallowed,
791
910
  docMediaElement,
792
911
  docMediaMissing,
912
+ docMediaPath,
793
913
  docMediaType,
794
914
  docTagUnallowed,
795
- docUnsupported,
796
915
  errorCode,
797
- gameExists,
916
+ gameHandleTaken,
917
+ gameIdMissing,
798
918
  gameNotDeployed,
799
919
  gameNotFound,
800
- gameRepoMissing,
801
920
  gameUnavailable,
802
- gitChangesPresent,
803
921
  gitRemoteMissing,
804
922
  gitRemoteUnreachable,
805
923
  gitRepoMissing,
806
924
  gitTagCreateFailed,
925
+ gitTagExists,
807
926
  gitTagPushFailed,
808
- gitVersionExists,
927
+ gitUncommitted,
809
928
  httpError,
810
929
  imageAspect,
811
930
  imageFound,
@@ -816,30 +935,40 @@ module.exports = {
816
935
  imageUnsupported,
817
936
  imageWidthMax,
818
937
  imageWidthMin,
938
+ initialized,
819
939
  issuesAfterBuild,
820
940
  issuesFound,
941
+ listed,
942
+ lockfileMissing,
943
+ loggedIn,
944
+ loggedOut,
945
+ loggingIn,
821
946
  loginCodeExpired,
822
947
  loginFailed,
823
- noGameId,
824
- noLockFile,
825
- noManifest,
948
+ manifestMissing,
949
+ noAccount,
950
+ noIssuesFound,
826
951
  noProductionRelease,
827
- noTemplate,
828
952
  noVideoMetadata,
829
953
  noVideoTrack,
830
954
  notLoggedIn,
955
+ nothingToDo,
831
956
  openToContinue,
832
957
  opened,
833
- packageFileMissing,
834
958
  packageFileTooLarge,
959
+ packageIndexMissing,
835
960
  packageTooLarge,
836
961
  packageTooManyFiles,
837
962
  packageUncompressedTooLarge,
963
+ packed,
964
+ pressEnterToOpen,
838
965
  projectNotRepoRoot,
839
966
  releaseFailed,
967
+ releaseFailedHeading,
840
968
  releasePublished,
841
969
  releaseTimedOut,
842
970
  releaseWaiting,
971
+ repoNotConnected,
843
972
  sessionExpired,
844
973
  sizeB,
845
974
  sizeGb,
@@ -851,17 +980,19 @@ module.exports = {
851
980
  starterPixiJs,
852
981
  starterThreeJs,
853
982
  tagPushed,
983
+ templateMissing,
854
984
  templateUnavailable,
855
- timedOut,
856
985
  timeoutInvalid,
857
986
  unableToFind,
858
987
  unableToOpen,
859
988
  unableToParse,
860
989
  unableToReach,
990
+ unknownField,
991
+ unknownTable,
861
992
  uploadFailed,
862
993
  uploadRateLimited,
994
+ versionExists,
863
995
  versionPrerelease,
864
- versionPublished,
865
996
  versionRelease,
866
997
  versionUnchecked,
867
998
  videoAspect,
@@ -875,5 +1006,6 @@ module.exports = {
875
1006
  videoUnsupported,
876
1007
  videoWidthMax,
877
1008
  videoWidthMin,
1009
+ waitTimedOut,
878
1010
  workflowMissing,
879
1011
  };
@@ -14,13 +14,12 @@ const z = require("zod");
14
14
  const cfgai = require("../config/ai");
15
15
  const cfgaudience = require("../config/audience");
16
16
  const cfgcode = require("../config/code");
17
- const cfgdoc = require("../config/doc");
17
+ const cfgfile = require("../config/file");
18
18
  const cfggenre = require("../config/genre");
19
19
  const cfghtml = require("../config/html");
20
20
  const cfgimage = require("../config/image");
21
21
  const cfginput = require("../config/input");
22
22
  const cfgorientation = require("../config/orientation");
23
- const cfgproduct = require("../config/product");
24
23
  const cfgvideo = require("../config/video");
25
24
  const cfgvisibility = require("../config/visibility");
26
25
  const chars = require("../util/chars");
@@ -38,6 +37,7 @@ const cleanSemver = semver.clean;
38
37
  const docCheck = check.doc;
39
38
  const existsCheck = check.exists;
40
39
  const extname = path.extname;
40
+ const posixDirname = path.posix.dirname;
41
41
  const ISSUE = cfgcode.ISSUE;
42
42
  const imageCheck = check.image;
43
43
  const invalidChars = chars.invalid;
@@ -51,13 +51,12 @@ const videoCheck = check.video;
51
51
 
52
52
  // [declare]
53
53
 
54
- const HANDLE = cfgproduct.handle;
55
54
  const message = schemaMessage.messages("game");
56
55
 
57
56
  // [declare]
58
57
 
59
- const GALLERY = `.${HANDLE}/gallery`;
60
- const MEDIA = `.${HANDLE}`;
58
+ const GALLERY = cfgfile.GALLERY;
59
+ const MEDIA = cfgfile.MEDIA;
61
60
 
62
61
  // [declare]
63
62
 
@@ -144,15 +143,16 @@ const required = function (field, lines) {
144
143
  // [function]
145
144
 
146
145
  // a list of distinct values. Without a max, its length is up to the values.
147
- const zSet = function (field, schema, { max = Infinity, min }) {
148
- return z
149
- .array(schema)
150
- .min(min, atLeast(field, min, "element"))
151
- .max(max, message(field, [`Must not exceed: ${max} elements`]))
152
- .refine(
153
- (items) => new Set(items).size === items.length,
154
- message(field, ["Must not contain duplicate elements."], ISSUE.DUPLICATE),
155
- );
146
+ const zSet = function (field, schema, { max, min }) {
147
+ const list = z.array(schema).min(min, atLeast(field, min, "element"));
148
+ return (
149
+ max
150
+ ? list.max(max, message(field, [`Must not exceed: ${max} elements`]))
151
+ : list
152
+ ).refine(
153
+ (items) => new Set(items).size === items.length,
154
+ message(field, ["Must not contain duplicate elements."], ISSUE.DUPLICATE),
155
+ );
156
156
  };
157
157
 
158
158
  // [function]
@@ -174,11 +174,17 @@ const zSetBasefile = function (field, schema, { max, min }) {
174
174
 
175
175
  // [function]
176
176
 
177
+ // a string of `min` to `max` characters, counted as Arcadible counts them: a
178
+ // character outside the Basic Multilingual Plane, such as an emoji, is one,
179
+ // though JavaScript's length counts it as two.
177
180
  const zString = function (field, char, { max, min }) {
178
181
  return z
179
182
  .string()
180
183
  .min(min, { ...atLeast(field, min, "character"), abort: min === 1 })
181
- .max(max, message(field, [`Must not exceed: ${max} characters`]))
184
+ .refine(
185
+ (str) => [...str].length <= max,
186
+ message(field, [`Must not exceed: ${max} characters`], ISSUE.TOO_LONG),
187
+ )
182
188
  .superRefine((str, ctx) => {
183
189
  const invalid = invalidChars(str, char);
184
190
  if (invalid.length) {
@@ -197,8 +203,12 @@ const zString = function (field, char, { max, min }) {
197
203
 
198
204
  // [function]
199
205
 
206
+ // plain text, which can't be only spaces.
200
207
  const zText = function (field, opts) {
201
- return zString(field, /[\p{L}0-9 .,!?'"():;-]/u, opts);
208
+ return zString(field, /[\p{L}0-9 .,!?'"():;-]/u, opts).refine(
209
+ (str) => str.trim() !== "",
210
+ message(field, ["Must not be blank."], ISSUE.EMPTY),
211
+ );
202
212
  };
203
213
 
204
214
  // [function]
@@ -206,7 +216,7 @@ const zText = function (field, opts) {
206
216
  const zHtml = function (field, root, { max, min, ...opts }) {
207
217
  const char = /[\p{L}\p{M}\p{N}\p{P}\p{S}\p{Zs}\r\n]/u;
208
218
  return zString(field, char, { max, min }).superRefine((str, ctx) => {
209
- const issues = docCheck(root, str, { ...opts, type: cfgdoc[".html"] });
219
+ const issues = docCheck(root, str, opts);
210
220
  if (issues.length) {
211
221
  ctx.addIssue({
212
222
  code: "custom",
@@ -284,27 +294,34 @@ const zMp4 = function (field, root, opts) {
284
294
  const zPngMp4 = function (field, root, optsPng, optsMp4) {
285
295
  const pngSchema = zPng(field, root, optsPng);
286
296
  const mp4Schema = zMp4(field, root, optsMp4);
287
- return z.string().superRefine((file, ctx) => {
288
- const ext = extname(file);
289
- let schema = null;
290
- if (Object.hasOwn(cfgimage, ext)) {
291
- schema = pngSchema;
292
- }
293
- if (Object.hasOwn(cfgvideo, ext)) {
294
- schema = mp4Schema;
295
- }
296
- if (!schema) {
297
- ctx.addIssue({
298
- code: "custom",
299
- ...message(field, ["Must be a PNG or MP4 file."], ISSUE.MEDIA_INVALID),
300
- });
301
- return;
302
- }
303
- const result = schema.safeParse(file);
304
- if (!result.success) {
305
- result.error.issues.forEach((issue) => ctx.addIssue(issue));
306
- }
307
- });
297
+ return z
298
+ .string()
299
+ .min(1, { ...atLeast(field, 1), abort: true })
300
+ .superRefine((file, ctx) => {
301
+ const ext = extname(file);
302
+ let schema = null;
303
+ if (Object.hasOwn(cfgimage, ext)) {
304
+ schema = pngSchema;
305
+ }
306
+ if (Object.hasOwn(cfgvideo, ext)) {
307
+ schema = mp4Schema;
308
+ }
309
+ if (!schema) {
310
+ ctx.addIssue({
311
+ code: "custom",
312
+ ...message(
313
+ field,
314
+ ["Must be a PNG or MP4 file."],
315
+ ISSUE.MEDIA_INVALID,
316
+ ),
317
+ });
318
+ return;
319
+ }
320
+ const result = schema.safeParse(file);
321
+ if (!result.success) {
322
+ result.error.issues.forEach((issue) => ctx.addIssue(issue));
323
+ }
324
+ });
308
325
  };
309
326
 
310
327
  // [function]
@@ -383,6 +400,10 @@ const checkSrc = function (root, src, tag) {
383
400
  ) {
384
401
  return false;
385
402
  }
403
+ // the store page finds it by the gallery's path, exactly.
404
+ if (posixDirname(src) !== GALLERY) {
405
+ return phrase.docMediaPath(src, GALLERY);
406
+ }
386
407
  return existsCheck(root, src) || phrase.docMediaMissing(src);
387
408
  };
388
409
 
@@ -465,6 +486,7 @@ const zId = function () {
465
486
  .string(
466
487
  required(field, [`Run ${phrase.cmdInit()} to generate a new game ID.`]),
467
488
  )
489
+ .min(1, { ...atLeast(field, 1), abort: true })
468
490
  .uuid(message(field, ["Must be a valid UUID."]))
469
491
  .refine(
470
492
  (value) => value === value.toLowerCase(),
@@ -542,10 +564,11 @@ const zVersion = function () {
542
564
  .string(
543
565
  required(field, ["A version is required unless one can be derived."]),
544
566
  )
545
- .refine(
546
- (value) => validSemver(value) !== null,
547
- message(field, ["Must be valid semver."], ISSUE.VERSION_INVALID),
548
- )
567
+ .min(1, { ...atLeast(field, 1), abort: true })
568
+ .refine((value) => validSemver(value) !== null, {
569
+ ...message(field, ["Must be valid semver."], ISSUE.VERSION_INVALID),
570
+ abort: true,
571
+ })
549
572
  .refine(
550
573
  // lowercase too, since the database refuses an uppercase prerelease.
551
574
  (value) => cleanSemver(value) === value && value === value.toLowerCase(),
@@ -7,14 +7,17 @@ const z = require("zod");
7
7
  // [import]
8
8
 
9
9
  const cfgcode = require("../config/code");
10
+ const cfgfile = require("../config/file");
10
11
  const cfgproduct = require("../config/product");
11
12
  const check = require("./check/workflow");
13
+ const commands = require("../util/command");
12
14
  const phrase = require("../phrase");
13
15
  const schemaMessage = require("./message");
14
16
 
15
17
  // [use]
16
18
 
17
19
  const avoidsCheck = check.avoids;
20
+ const buildCommand = commands.buildCommand;
18
21
  const ISSUE = cfgcode.ISSUE;
19
22
  const existsCheck = check.exists;
20
23
  const hasFileCheck = check.hasFile;
@@ -25,20 +28,16 @@ const lacksFilesCheck = check.lacksFiles;
25
28
 
26
29
  // [declare]
27
30
 
28
- const HANDLE = cfgproduct.handle;
29
31
  const message = schemaMessage.messages("workflow");
30
- const MEDIA = `.${HANDLE}`;
32
+ const MEDIA = cfgfile.MEDIA;
31
33
  const INDEX = "index.html";
32
- const RESERVED = [`${HANDLE}.json`, MEDIA];
34
+ const RESERVED = [cfgfile.PACKAGED, MEDIA];
33
35
 
34
36
  // [function]
35
37
 
36
- // whether `build` runs anything: a command that isn't blank, as `arc build`
37
- // trims and drops blank ones (action/build/tool.js).
38
+ // whether `build` runs anything, as `arc build` runs it.
38
39
  const hasBuild = function (build) {
39
- return [build]
40
- .flat()
41
- .some((cmd) => typeof cmd === "string" && cmd.trim() !== "");
40
+ return buildCommand(build) !== "";
42
41
  };
43
42
 
44
43
  // [function]