arcadible 0.1.42 → 0.1.43
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/README.md +1 -1
- package/bin/arc-build.js +3 -1
- package/bin/arc-deploy.js +20 -2
- package/bin/arc-pack.js +3 -1
- package/bin/arc-validate.js +12 -5
- package/package.json +1 -1
- package/src/action/validate/tool.js +7 -2
- package/src/action/validate/unknown.js +85 -0
- package/src/config/code.js +1 -0
- package/src/phrase.js +13 -1
- package/src/schema/game.js +21 -7
- package/src/util/log.js +9 -0
- package/src/util/output.js +14 -1
- package/src/util/strs.js +43 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ arc init star-drift && cd star-drift
|
|
|
35
35
|
arc deploy
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Your repository is the store page: `arcadible.toml` holds the game's settings, `README.md` its title and description, and `.arcadible/` its art. `arc deploy` validates, builds, packages, and publishes it, then waits until the release is live.
|
|
38
|
+
Your repository is the store page: `arcadible.toml` holds the game's settings, `README.md` its title and description, and `.arcadible/` its art. `arc deploy` validates, builds, packages, and publishes it, then waits until the release is live. A new project starts at a prerelease version, `0.1.0-beta.1`, so its first deploy goes to staging: a link to play and share, before players see it.
|
|
39
39
|
|
|
40
40
|
## Commands
|
|
41
41
|
|
package/bin/arc-build.js
CHANGED
|
@@ -35,6 +35,7 @@ const print = output.print;
|
|
|
35
35
|
const relative = path.relative;
|
|
36
36
|
const resolve = path.resolve;
|
|
37
37
|
const SpinnerWheel = spinner.SpinnerWheel;
|
|
38
|
+
const warn = output.warn;
|
|
38
39
|
|
|
39
40
|
// [declare]
|
|
40
41
|
|
|
@@ -65,7 +66,7 @@ const start = function (argv) {
|
|
|
65
66
|
parsed = proj;
|
|
66
67
|
return validate({ project: parsed, root });
|
|
67
68
|
})
|
|
68
|
-
.then((issues) => {
|
|
69
|
+
.then(({ issues, warnings }) => {
|
|
69
70
|
const count = issues.length;
|
|
70
71
|
if (count) {
|
|
71
72
|
throw new ArcError(
|
|
@@ -75,6 +76,7 @@ const start = function (argv) {
|
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
sw.stop(true);
|
|
79
|
+
warn(warnings, { json, quiet });
|
|
78
80
|
sw = new SpinnerWheel("Building... %s");
|
|
79
81
|
sw.start(json || quiet);
|
|
80
82
|
return build({ project: parsed, root });
|
package/bin/arc-deploy.js
CHANGED
|
@@ -41,6 +41,7 @@ const print = output.print;
|
|
|
41
41
|
const ProgressBar = progress.ProgressBar;
|
|
42
42
|
const resolve = path.resolve;
|
|
43
43
|
const SpinnerWheel = spinner.SpinnerWheel;
|
|
44
|
+
const warn = output.warn;
|
|
44
45
|
|
|
45
46
|
// [declare]
|
|
46
47
|
|
|
@@ -260,7 +261,7 @@ const start = function (argv) {
|
|
|
260
261
|
parsed = proj;
|
|
261
262
|
return validate({ project: parsed, root });
|
|
262
263
|
})
|
|
263
|
-
.then((issues) => {
|
|
264
|
+
.then(({ issues, warnings }) => {
|
|
264
265
|
const count = issues.length;
|
|
265
266
|
if (count) {
|
|
266
267
|
throw new ArcError(
|
|
@@ -270,6 +271,7 @@ const start = function (argv) {
|
|
|
270
271
|
);
|
|
271
272
|
}
|
|
272
273
|
sw.stop(true);
|
|
274
|
+
warn(warnings, { json, quiet });
|
|
273
275
|
sw = new SpinnerWheel("Checking... %s");
|
|
274
276
|
// git deploys need no login: anyone who can push to the repository can
|
|
275
277
|
// release, and the Arcadible GitHub App publishes the release. Anyone
|
|
@@ -314,7 +316,23 @@ const start = function (argv) {
|
|
|
314
316
|
.then(([current, artifact]) =>
|
|
315
317
|
Promise.all([current, artifact, artifact ? project(root) : parsed]),
|
|
316
318
|
)
|
|
317
|
-
.then(([current, artifact, proj]) =>
|
|
319
|
+
.then(([current, artifact, proj]) =>
|
|
320
|
+
Promise.all([
|
|
321
|
+
current,
|
|
322
|
+
artifact,
|
|
323
|
+
proj,
|
|
324
|
+
proj === parsed ? null : validate({ project: proj, root }),
|
|
325
|
+
]),
|
|
326
|
+
)
|
|
327
|
+
.then(([current, artifact, proj, validated]) => {
|
|
328
|
+
const count = validated?.issues.length ?? 0;
|
|
329
|
+
if (count) {
|
|
330
|
+
throw new ArcError(
|
|
331
|
+
cfgcode.VALIDATION_FAILED,
|
|
332
|
+
phrase.issuesAfterBuild(count),
|
|
333
|
+
{ data: { issues: validated.issues } },
|
|
334
|
+
);
|
|
335
|
+
}
|
|
318
336
|
parsed = proj;
|
|
319
337
|
sw.stop(true);
|
|
320
338
|
sw = new SpinnerWheel("Packing... %s");
|
package/bin/arc-pack.js
CHANGED
|
@@ -34,6 +34,7 @@ const findRc = rc.findRc;
|
|
|
34
34
|
const print = output.print;
|
|
35
35
|
const resolve = path.resolve;
|
|
36
36
|
const SpinnerWheel = spinner.SpinnerWheel;
|
|
37
|
+
const warn = output.warn;
|
|
37
38
|
|
|
38
39
|
// [declare]
|
|
39
40
|
|
|
@@ -66,7 +67,7 @@ const start = function (argv) {
|
|
|
66
67
|
parsed = proj;
|
|
67
68
|
return validate({ project: parsed, root });
|
|
68
69
|
})
|
|
69
|
-
.then((issues) => {
|
|
70
|
+
.then(({ issues, warnings }) => {
|
|
70
71
|
const count = issues.length;
|
|
71
72
|
if (count) {
|
|
72
73
|
throw new ArcError(
|
|
@@ -76,6 +77,7 @@ const start = function (argv) {
|
|
|
76
77
|
);
|
|
77
78
|
}
|
|
78
79
|
sw.stop(true);
|
|
80
|
+
warn(warnings, { json, quiet });
|
|
79
81
|
sw = new SpinnerWheel("Packing... %s");
|
|
80
82
|
sw.start(json || quiet);
|
|
81
83
|
return pack({
|
package/bin/arc-validate.js
CHANGED
|
@@ -28,6 +28,7 @@ const dereference = schema.dereference;
|
|
|
28
28
|
const findRc = rc.findRc;
|
|
29
29
|
const print = output.print;
|
|
30
30
|
const resolve = path.resolve;
|
|
31
|
+
const schemaPrint = schema.print;
|
|
31
32
|
|
|
32
33
|
// [declare]
|
|
33
34
|
|
|
@@ -48,24 +49,30 @@ const conf = {
|
|
|
48
49
|
const start = function (argv) {
|
|
49
50
|
const json = argv.json;
|
|
50
51
|
return validate({ root: resolve(process.cwd(), argv.cwd) })
|
|
51
|
-
.then((issues) => {
|
|
52
|
+
.then(({ issues, warnings }) => {
|
|
52
53
|
const count = issues.length;
|
|
53
54
|
if (json) {
|
|
54
|
-
print({ issues });
|
|
55
|
-
} else
|
|
55
|
+
print({ issues, warnings });
|
|
56
|
+
} else {
|
|
56
57
|
const [probs, refs] = dereference(issues);
|
|
57
58
|
const probcount = probs.length;
|
|
58
59
|
if (probcount) {
|
|
59
60
|
log.hdo(`${probcount} ${probcount === 1 ? "issue" : "issues"}:`);
|
|
60
61
|
log.p(probs.map((prob) => `✗ ${prob}`));
|
|
61
62
|
}
|
|
63
|
+
const warncount = argv.quiet ? 0 : warnings.length;
|
|
64
|
+
if (warncount) {
|
|
65
|
+
log.hwo(`${warncount} ${warncount === 1 ? "warning" : "warnings"}:`);
|
|
66
|
+
log.p(warnings.map((each) => `⚠ ${schemaPrint(each)}`));
|
|
67
|
+
}
|
|
62
68
|
const refcount = refs.length;
|
|
63
69
|
if (refcount) {
|
|
64
70
|
log.h(`${refcount} ${refcount === 1 ? "reference" : "references"}:`);
|
|
65
71
|
log.p(refs);
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
if (!count && !argv.quiet) {
|
|
74
|
+
log.hs("No issues found.");
|
|
75
|
+
}
|
|
69
76
|
}
|
|
70
77
|
process.exit(count ? 1 : 0);
|
|
71
78
|
})
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ const readme = require("../../schema/readme");
|
|
|
8
8
|
const readmeFile = require("../../parser/readme-file");
|
|
9
9
|
const schema = require("../../util/schema");
|
|
10
10
|
const sections = require("../../parser/sections");
|
|
11
|
+
const unknown = require("./unknown");
|
|
11
12
|
const workflow = require("../../schema/workflow");
|
|
12
13
|
|
|
13
14
|
// [use]
|
|
@@ -49,10 +50,14 @@ const validate = function (proj, readmeStructure, { root }) {
|
|
|
49
50
|
|
|
50
51
|
// [function]
|
|
51
52
|
|
|
53
|
+
// the project's issues, which fail it, and its warnings, which don't.
|
|
52
54
|
const main = function (conf) {
|
|
53
55
|
const root = conf.root;
|
|
54
|
-
return Promise.all([given(conf), structure(root)]).then(
|
|
55
|
-
([proj, readmeStructure]) =>
|
|
56
|
+
return Promise.all([given(conf), structure(root), unknown(root)]).then(
|
|
57
|
+
([proj, readmeStructure, warnings]) => ({
|
|
58
|
+
issues: validate(proj, readmeStructure, { root }),
|
|
59
|
+
warnings: warnings.sort(schema.compare),
|
|
60
|
+
}),
|
|
56
61
|
);
|
|
57
62
|
};
|
|
58
63
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// [import]
|
|
4
|
+
|
|
5
|
+
const cfgcode = require("../../config/code");
|
|
6
|
+
const game = require("../../schema/game");
|
|
7
|
+
const manifest = require("../../parser/manifest");
|
|
8
|
+
const strs = require("../../util/strs");
|
|
9
|
+
const workflow = require("../../schema/workflow");
|
|
10
|
+
|
|
11
|
+
// [use]
|
|
12
|
+
|
|
13
|
+
const closest = strs.closest;
|
|
14
|
+
const ISSUE = cfgcode.ISSUE;
|
|
15
|
+
|
|
16
|
+
// [function]
|
|
17
|
+
|
|
18
|
+
// the fields each table takes, from its schema.
|
|
19
|
+
const known = function (root) {
|
|
20
|
+
return {
|
|
21
|
+
game: Object.keys(game({}, { root }).shape),
|
|
22
|
+
workflow: Object.keys(workflow({}, { root }).shape),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// [function]
|
|
27
|
+
|
|
28
|
+
// a warning, in an issue's shape: Arcadible ignores what it doesn't know, so
|
|
29
|
+
// it's reported without failing, with the name it might have meant.
|
|
30
|
+
const warning = function (path, message, near) {
|
|
31
|
+
return {
|
|
32
|
+
code: ISSUE.FIELD_UNKNOWN,
|
|
33
|
+
details: near ? [`Did you mean: ${near}?`] : [],
|
|
34
|
+
link: null,
|
|
35
|
+
message,
|
|
36
|
+
path,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// [function]
|
|
41
|
+
|
|
42
|
+
// a value outside any table, such as a `handle` above `[game]`: the field
|
|
43
|
+
// it is, in the table that takes it, if any.
|
|
44
|
+
const untabled = function (fields, name) {
|
|
45
|
+
const table = Object.keys(fields).find((each) => fields[each].includes(name));
|
|
46
|
+
return warning(name, "Unknown field.", table ? `${table}.${name}` : null);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// [function]
|
|
50
|
+
|
|
51
|
+
const isTable = function (value) {
|
|
52
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// [function]
|
|
56
|
+
|
|
57
|
+
// the tables and fields in arcadible.toml that Arcadible doesn't know, such
|
|
58
|
+
// as a misspelled `genre`, as warnings.
|
|
59
|
+
const main = function (root) {
|
|
60
|
+
const fields = known(root);
|
|
61
|
+
const tables = Object.keys(fields);
|
|
62
|
+
return manifest(root).then((conf) =>
|
|
63
|
+
Object.entries(conf).flatMap(([table, values]) => {
|
|
64
|
+
if (!isTable(values)) {
|
|
65
|
+
return Object.hasOwn(fields, table) ? [] : [untabled(fields, table)];
|
|
66
|
+
}
|
|
67
|
+
if (!Object.hasOwn(fields, table)) {
|
|
68
|
+
return [warning(table, "Unknown table.", closest(table, tables))];
|
|
69
|
+
}
|
|
70
|
+
return Object.keys(values)
|
|
71
|
+
.filter((field) => !fields[table].includes(field))
|
|
72
|
+
.map((field) =>
|
|
73
|
+
warning(
|
|
74
|
+
`${table}.${field}`,
|
|
75
|
+
"Unknown field.",
|
|
76
|
+
closest(field, fields[table]),
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// [export]
|
|
84
|
+
|
|
85
|
+
module.exports = main;
|
package/src/config/code.js
CHANGED
package/src/phrase.js
CHANGED
|
@@ -216,7 +216,7 @@ const gitRemoteMissing = function () {
|
|
|
216
216
|
|
|
217
217
|
const noManifest = function (file, dir, found) {
|
|
218
218
|
return blocks(
|
|
219
|
-
dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}
|
|
219
|
+
dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}.`,
|
|
220
220
|
found
|
|
221
221
|
? `The project is in \`${found}\`: run arc there, or pass \`-C ${found}\`.`
|
|
222
222
|
: "Run arc in the project's directory, or pass -C, or start a project with `arc init`.",
|
|
@@ -337,6 +337,17 @@ const timeoutInvalid = function () {
|
|
|
337
337
|
|
|
338
338
|
// [function]
|
|
339
339
|
|
|
340
|
+
// issues in what the build wrote, such as its art, once arc deploy has
|
|
341
|
+
// parsed the project again.
|
|
342
|
+
const issuesAfterBuild = function (count) {
|
|
343
|
+
return blocks(
|
|
344
|
+
`${count} ${count === 1 ? "issue" : "issues"} found after the build.`,
|
|
345
|
+
`The build writes files the manifest reads. Run ${cmdValidate()} for details.`,
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// [function]
|
|
350
|
+
|
|
340
351
|
const issuesFound = function (count) {
|
|
341
352
|
return blocks(
|
|
342
353
|
`${count} ${count === 1 ? "issue" : "issues"} found.`,
|
|
@@ -786,6 +797,7 @@ module.exports = {
|
|
|
786
797
|
imageUnsupported,
|
|
787
798
|
imageWidthMax,
|
|
788
799
|
imageWidthMin,
|
|
800
|
+
issuesAfterBuild,
|
|
789
801
|
issuesFound,
|
|
790
802
|
loginExpired,
|
|
791
803
|
loginFailed,
|
package/src/schema/game.js
CHANGED
|
@@ -177,7 +177,7 @@ const zSetBasefile = function (field, schema, { max, min }) {
|
|
|
177
177
|
const zString = function (field, char, { max, min }) {
|
|
178
178
|
return z
|
|
179
179
|
.string()
|
|
180
|
-
.min(min, atLeast(field, min, "character"))
|
|
180
|
+
.min(min, { ...atLeast(field, min, "character"), abort: min === 1 })
|
|
181
181
|
.max(max, message(field, [`Must not exceed: ${max} characters`]))
|
|
182
182
|
.superRefine((str, ctx) => {
|
|
183
183
|
const invalid = invalidChars(str, char);
|
|
@@ -430,15 +430,29 @@ const zGenres = function () {
|
|
|
430
430
|
|
|
431
431
|
// [function]
|
|
432
432
|
|
|
433
|
+
// each of a handle's rules on its own, so a handle lists every one it breaks:
|
|
434
|
+
// its length, its characters, and its hyphens. The database's
|
|
435
|
+
// check (db/check/game.sql) is the same rules in one regex.
|
|
433
436
|
const zHandle = function () {
|
|
434
437
|
const field = "handle";
|
|
435
438
|
return z.optional(
|
|
436
|
-
|
|
437
|
-
.
|
|
438
|
-
.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
439
|
+
zString(field, /[a-z0-9-]/u, { max: 24, min: 1 })
|
|
440
|
+
.min(3, atLeast(field, 3, "characters"))
|
|
441
|
+
.refine(
|
|
442
|
+
(str) => !str.startsWith("-") && !str.endsWith("-"),
|
|
443
|
+
message(
|
|
444
|
+
field,
|
|
445
|
+
["Must not start or end with a hyphen."],
|
|
446
|
+
ISSUE.CHARACTERS_INVALID,
|
|
447
|
+
),
|
|
448
|
+
)
|
|
449
|
+
.refine(
|
|
450
|
+
(str) => !str.includes("--"),
|
|
451
|
+
message(
|
|
452
|
+
field,
|
|
453
|
+
["Must not contain consecutive hyphens."],
|
|
454
|
+
ISSUE.CHARACTERS_INVALID,
|
|
455
|
+
),
|
|
442
456
|
),
|
|
443
457
|
);
|
|
444
458
|
};
|
package/src/util/log.js
CHANGED
|
@@ -82,6 +82,14 @@ const hdo = function (message) {
|
|
|
82
82
|
|
|
83
83
|
// [function]
|
|
84
84
|
|
|
85
|
+
// a warning heading on stdout, for when the warnings are part of the result,
|
|
86
|
+
// as with arc validate's.
|
|
87
|
+
const hwo = function (message) {
|
|
88
|
+
log(console.log, message, { bold: true, color: "yellow" });
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// [function]
|
|
92
|
+
|
|
85
93
|
const pw = function (message) {
|
|
86
94
|
log(console.warn, message, { color: "yellow" });
|
|
87
95
|
};
|
|
@@ -122,6 +130,7 @@ module.exports = {
|
|
|
122
130
|
hd,
|
|
123
131
|
hdo,
|
|
124
132
|
hs,
|
|
133
|
+
hwo,
|
|
125
134
|
nlhd,
|
|
126
135
|
nlp,
|
|
127
136
|
nlpd,
|
package/src/util/output.js
CHANGED
|
@@ -11,6 +11,7 @@ const error = require("./error");
|
|
|
11
11
|
const log = require("./log");
|
|
12
12
|
const objs = require("./objs");
|
|
13
13
|
const phrase = require("../phrase");
|
|
14
|
+
const schema = require("./schema");
|
|
14
15
|
const TOML = require("./toml");
|
|
15
16
|
|
|
16
17
|
// [use]
|
|
@@ -106,6 +107,18 @@ const abort = function (
|
|
|
106
107
|
process.exit(1);
|
|
107
108
|
};
|
|
108
109
|
|
|
110
|
+
// [function]
|
|
111
|
+
|
|
112
|
+
// arc validate's warnings, such as an unknown field, on stderr in yellow,
|
|
113
|
+
// before a command that validates goes on. Left out with `--json` and
|
|
114
|
+
// `--quiet`; arc validate --json lists them.
|
|
115
|
+
const warn = function (warnings, { json, quiet } = {}) {
|
|
116
|
+
if (json || quiet) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
warnings.forEach((each) => log.pw(`⚠ ${schema.print(each)}`));
|
|
120
|
+
};
|
|
121
|
+
|
|
109
122
|
// [export]
|
|
110
123
|
|
|
111
|
-
module.exports = { abort, print, printJson, printToml };
|
|
124
|
+
module.exports = { abort, print, printJson, printToml, warn };
|
package/src/util/strs.js
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
// [function]
|
|
4
4
|
|
|
5
|
+
// the number of single-character edits between two strings (Levenshtein).
|
|
6
|
+
const distance = function (a, b) {
|
|
7
|
+
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
8
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
9
|
+
const next = [i];
|
|
10
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
11
|
+
next[j] = Math.min(
|
|
12
|
+
row[j] + 1,
|
|
13
|
+
next[j - 1] + 1,
|
|
14
|
+
row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
row = next;
|
|
18
|
+
}
|
|
19
|
+
return row[b.length];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// [function]
|
|
23
|
+
|
|
24
|
+
// the candidate nearest `str`, for "Did you mean", or null when none is
|
|
25
|
+
// within two edits.
|
|
26
|
+
const closest = function (str, candidates) {
|
|
27
|
+
let best = null;
|
|
28
|
+
let min = 3;
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
const d = distance(str, candidate);
|
|
31
|
+
if (d < min) {
|
|
32
|
+
best = candidate;
|
|
33
|
+
min = d;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// [function]
|
|
40
|
+
|
|
5
41
|
const indentLines = function (strs, spaces = 2) {
|
|
6
42
|
const pad = " ".repeat(spaces);
|
|
7
43
|
return strs
|
|
@@ -28,4 +64,10 @@ const trimWhitespace = function (str) {
|
|
|
28
64
|
|
|
29
65
|
// [export]
|
|
30
66
|
|
|
31
|
-
module.exports = {
|
|
67
|
+
module.exports = {
|
|
68
|
+
closest,
|
|
69
|
+
distance,
|
|
70
|
+
indentLines,
|
|
71
|
+
stripLeadingInvisible,
|
|
72
|
+
trimWhitespace,
|
|
73
|
+
};
|