arcadible 0.1.42 → 0.1.44
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 +22 -5
- package/bin/arc-deploy.js +43 -12
- package/bin/arc-pack.js +12 -5
- package/bin/arc-validate.js +12 -5
- package/package.json +1 -1
- package/src/action/login/flow.js +5 -2
- package/src/action/pack/tool.js +20 -6
- package/src/action/predeploy/git.js +22 -5
- package/src/action/predeploy/tool-git.js +1 -0
- package/src/action/validate/tool.js +7 -2
- package/src/action/validate/unknown.js +85 -0
- package/src/config/code.js +25 -9
- package/src/phrase.js +39 -8
- package/src/schema/game.js +21 -7
- package/src/util/error.js +5 -4
- package/src/util/log.js +9 -0
- package/src/util/output.js +35 -9
- package/src/util/request.js +2 -2
- package/src/util/s3.js +2 -2
- 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,8 @@ 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;
|
|
39
|
+
const warning = output.warning;
|
|
38
40
|
|
|
39
41
|
// [declare]
|
|
40
42
|
|
|
@@ -58,6 +60,8 @@ const start = function (argv) {
|
|
|
58
60
|
const root = resolve(process.cwd(), argv.cwd);
|
|
59
61
|
// the project, parsed once for both steps.
|
|
60
62
|
let parsed = null;
|
|
63
|
+
// what --json lists under `warnings`.
|
|
64
|
+
let warnings = [];
|
|
61
65
|
let sw = new SpinnerWheel("Validating... %s");
|
|
62
66
|
sw.start(json || quiet);
|
|
63
67
|
return project(root)
|
|
@@ -65,23 +69,34 @@ const start = function (argv) {
|
|
|
65
69
|
parsed = proj;
|
|
66
70
|
return validate({ project: parsed, root });
|
|
67
71
|
})
|
|
68
|
-
.then((
|
|
69
|
-
const count = issues.length;
|
|
72
|
+
.then((validated) => {
|
|
73
|
+
const count = validated.issues.length;
|
|
74
|
+
warnings = validated.warnings;
|
|
70
75
|
if (count) {
|
|
71
76
|
throw new ArcError(
|
|
72
77
|
cfgcode.VALIDATION_FAILED,
|
|
73
78
|
phrase.issuesFound(count),
|
|
74
|
-
{ data: { issues } },
|
|
79
|
+
{ data: { issues: validated.issues } },
|
|
75
80
|
);
|
|
76
81
|
}
|
|
77
82
|
sw.stop(true);
|
|
83
|
+
warn(warnings, { json, quiet });
|
|
78
84
|
sw = new SpinnerWheel("Building... %s");
|
|
79
85
|
sw.start(json || quiet);
|
|
80
86
|
return build({ project: parsed, root });
|
|
81
87
|
})
|
|
82
88
|
.then((artifact) => {
|
|
83
89
|
if (json) {
|
|
84
|
-
|
|
90
|
+
const missing = artifact?.missing
|
|
91
|
+
? [
|
|
92
|
+
warning(
|
|
93
|
+
cfgcode.WARNING.DEPLOY_DIR_NOT_BUILT,
|
|
94
|
+
phrase.buildOutputMissing(relative(root, artifact.missing)),
|
|
95
|
+
"workflow.deploy_dir",
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
: [];
|
|
99
|
+
print({ artifact, warnings: [...warnings, ...missing] });
|
|
85
100
|
return;
|
|
86
101
|
}
|
|
87
102
|
if (!quiet) {
|
|
@@ -101,7 +116,9 @@ const start = function (argv) {
|
|
|
101
116
|
log.hs("Nothing to do.");
|
|
102
117
|
}
|
|
103
118
|
})
|
|
104
|
-
.catch((ex) =>
|
|
119
|
+
.catch((ex) =>
|
|
120
|
+
abort("Build aborted.", ex, { data: { warnings }, json, sw }),
|
|
121
|
+
);
|
|
105
122
|
};
|
|
106
123
|
|
|
107
124
|
// [function]
|
package/bin/arc-deploy.js
CHANGED
|
@@ -41,6 +41,8 @@ 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;
|
|
45
|
+
const warning = output.warning;
|
|
44
46
|
|
|
45
47
|
// [declare]
|
|
46
48
|
|
|
@@ -64,7 +66,9 @@ const conf = {
|
|
|
64
66
|
|
|
65
67
|
// [function]
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
// `warnings` collects what --json lists, such as a version that couldn't be
|
|
70
|
+
// checked.
|
|
71
|
+
const handler = function ({ json, pb, quiet, sw, warnings }) {
|
|
68
72
|
return {
|
|
69
73
|
onAccountRefresh(refresh) {
|
|
70
74
|
sw.start(json || quiet);
|
|
@@ -75,7 +79,10 @@ const handler = function ({ json, pb, quiet, sw }) {
|
|
|
75
79
|
pb.start();
|
|
76
80
|
}
|
|
77
81
|
},
|
|
78
|
-
onLogInfo({ level, message }) {
|
|
82
|
+
onLogInfo({ code, level, message }) {
|
|
83
|
+
if (code) {
|
|
84
|
+
warnings.push(warning(code, message));
|
|
85
|
+
}
|
|
79
86
|
if (!json && !quiet) {
|
|
80
87
|
switch (level) {
|
|
81
88
|
case "warn":
|
|
@@ -252,6 +259,8 @@ const start = function (argv) {
|
|
|
252
259
|
// the project, parsed once and given to each step. A build can write files
|
|
253
260
|
// the manifest reads, such as art, so it's parsed again after one runs.
|
|
254
261
|
let parsed = null;
|
|
262
|
+
// what --json lists under `warnings`: arc validate's, then the deploy's.
|
|
263
|
+
const warnings = [];
|
|
255
264
|
let sw = new SpinnerWheel("Validating... %s");
|
|
256
265
|
sw.start(json || quiet);
|
|
257
266
|
return checkTimeout(timeout)
|
|
@@ -260,25 +269,29 @@ const start = function (argv) {
|
|
|
260
269
|
parsed = proj;
|
|
261
270
|
return validate({ project: parsed, root });
|
|
262
271
|
})
|
|
263
|
-
.then((
|
|
264
|
-
const count = issues.length;
|
|
272
|
+
.then((validated) => {
|
|
273
|
+
const count = validated.issues.length;
|
|
274
|
+
warnings.push(...validated.warnings);
|
|
265
275
|
if (count) {
|
|
266
276
|
throw new ArcError(
|
|
267
277
|
cfgcode.VALIDATION_FAILED,
|
|
268
278
|
phrase.issuesFound(count),
|
|
269
|
-
{ data: { issues } },
|
|
279
|
+
{ data: { issues: validated.issues } },
|
|
270
280
|
);
|
|
271
281
|
}
|
|
272
282
|
sw.stop(true);
|
|
283
|
+
warn(validated.warnings, { json, quiet });
|
|
273
284
|
sw = new SpinnerWheel("Checking... %s");
|
|
274
285
|
// git deploys need no login: anyone who can push to the repository can
|
|
275
286
|
// release, and the Arcadible GitHub App publishes the release. Anyone
|
|
276
287
|
// can wait for it too; the game's owner, logged in, waits among their
|
|
277
288
|
// own releases, which also say when the repository isn't connected.
|
|
278
289
|
if (git) {
|
|
279
|
-
return whoamiIfAny({
|
|
290
|
+
return whoamiIfAny({
|
|
291
|
+
handler: handler({ json, pb, quiet, sw, warnings }),
|
|
292
|
+
});
|
|
280
293
|
}
|
|
281
|
-
return whoami({ handler: handler({ json, pb, quiet, sw }) });
|
|
294
|
+
return whoami({ handler: handler({ json, pb, quiet, sw, warnings }) });
|
|
282
295
|
})
|
|
283
296
|
.then((current) => {
|
|
284
297
|
if (!current && !git) {
|
|
@@ -293,7 +306,7 @@ const start = function (argv) {
|
|
|
293
306
|
predeploy({
|
|
294
307
|
current,
|
|
295
308
|
git,
|
|
296
|
-
handler: handler({ json, pb, quiet, sw }),
|
|
309
|
+
handler: handler({ json, pb, quiet, sw, warnings }),
|
|
297
310
|
project: parsed,
|
|
298
311
|
root,
|
|
299
312
|
}),
|
|
@@ -314,7 +327,23 @@ const start = function (argv) {
|
|
|
314
327
|
.then(([current, artifact]) =>
|
|
315
328
|
Promise.all([current, artifact, artifact ? project(root) : parsed]),
|
|
316
329
|
)
|
|
317
|
-
.then(([current, artifact, proj]) =>
|
|
330
|
+
.then(([current, artifact, proj]) =>
|
|
331
|
+
Promise.all([
|
|
332
|
+
current,
|
|
333
|
+
artifact,
|
|
334
|
+
proj,
|
|
335
|
+
proj === parsed ? null : validate({ project: proj, root }),
|
|
336
|
+
]),
|
|
337
|
+
)
|
|
338
|
+
.then(([current, artifact, proj, validated]) => {
|
|
339
|
+
const count = validated?.issues.length ?? 0;
|
|
340
|
+
if (count) {
|
|
341
|
+
throw new ArcError(
|
|
342
|
+
cfgcode.VALIDATION_FAILED,
|
|
343
|
+
phrase.issuesAfterBuild(count),
|
|
344
|
+
{ data: { issues: validated.issues } },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
318
347
|
parsed = proj;
|
|
319
348
|
sw.stop(true);
|
|
320
349
|
sw = new SpinnerWheel("Packing... %s");
|
|
@@ -341,7 +370,7 @@ const start = function (argv) {
|
|
|
341
370
|
artifact,
|
|
342
371
|
current,
|
|
343
372
|
git,
|
|
344
|
-
handler: handler({ json, pb, quiet, sw }),
|
|
373
|
+
handler: handler({ json, pb, quiet, sw, warnings }),
|
|
345
374
|
project: parsed,
|
|
346
375
|
root,
|
|
347
376
|
}),
|
|
@@ -358,7 +387,7 @@ const start = function (argv) {
|
|
|
358
387
|
const shown = { ...deployed };
|
|
359
388
|
delete shown.game;
|
|
360
389
|
delete shown.release;
|
|
361
|
-
const result = git ? shown : { built, ...shown };
|
|
390
|
+
const result = { ...(git ? shown : { built, ...shown }), warnings };
|
|
362
391
|
sw.stop(true);
|
|
363
392
|
if (argv.wait) {
|
|
364
393
|
return waitAndReport(current, deployed, result, {
|
|
@@ -378,7 +407,9 @@ const start = function (argv) {
|
|
|
378
407
|
}
|
|
379
408
|
return null;
|
|
380
409
|
})
|
|
381
|
-
.catch((ex) =>
|
|
410
|
+
.catch((ex) =>
|
|
411
|
+
abort("Deploy aborted.", ex, { data: { warnings }, json, pb, sw }),
|
|
412
|
+
);
|
|
382
413
|
};
|
|
383
414
|
|
|
384
415
|
// [function]
|
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
|
|
|
@@ -59,6 +60,8 @@ const start = function (argv) {
|
|
|
59
60
|
const root = resolve(process.cwd(), argv.cwd);
|
|
60
61
|
// the project, parsed once for both steps.
|
|
61
62
|
let parsed = null;
|
|
63
|
+
// what --json lists under `warnings`.
|
|
64
|
+
let warnings = [];
|
|
62
65
|
let sw = new SpinnerWheel("Validating... %s");
|
|
63
66
|
sw.start(json || quiet);
|
|
64
67
|
return project(root)
|
|
@@ -66,16 +69,18 @@ const start = function (argv) {
|
|
|
66
69
|
parsed = proj;
|
|
67
70
|
return validate({ project: parsed, root });
|
|
68
71
|
})
|
|
69
|
-
.then((
|
|
70
|
-
const count = issues.length;
|
|
72
|
+
.then((validated) => {
|
|
73
|
+
const count = validated.issues.length;
|
|
74
|
+
warnings = validated.warnings;
|
|
71
75
|
if (count) {
|
|
72
76
|
throw new ArcError(
|
|
73
77
|
cfgcode.VALIDATION_FAILED,
|
|
74
78
|
phrase.issuesFound(count),
|
|
75
|
-
{ data: { issues } },
|
|
79
|
+
{ data: { issues: validated.issues } },
|
|
76
80
|
);
|
|
77
81
|
}
|
|
78
82
|
sw.stop(true);
|
|
83
|
+
warn(warnings, { json, quiet });
|
|
79
84
|
sw = new SpinnerWheel("Packing... %s");
|
|
80
85
|
sw.start(json || quiet);
|
|
81
86
|
return pack({
|
|
@@ -87,7 +92,7 @@ const start = function (argv) {
|
|
|
87
92
|
})
|
|
88
93
|
.then((artifact) => {
|
|
89
94
|
if (json) {
|
|
90
|
-
print({ artifact });
|
|
95
|
+
print({ artifact, warnings });
|
|
91
96
|
return;
|
|
92
97
|
}
|
|
93
98
|
if (!quiet) {
|
|
@@ -96,7 +101,9 @@ const start = function (argv) {
|
|
|
96
101
|
print(artifact, { nl: true, toml: true });
|
|
97
102
|
}
|
|
98
103
|
})
|
|
99
|
-
.catch((ex) =>
|
|
104
|
+
.catch((ex) =>
|
|
105
|
+
abort("Pack aborted.", ex, { data: { warnings }, json, sw }),
|
|
106
|
+
);
|
|
100
107
|
};
|
|
101
108
|
|
|
102
109
|
// [function]
|
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
package/src/action/login/flow.js
CHANGED
|
@@ -28,7 +28,7 @@ const POLL = {
|
|
|
28
28
|
interval: time.s(15),
|
|
29
29
|
maxDelay: time.s(5),
|
|
30
30
|
timeoutError: () =>
|
|
31
|
-
new ArcError(cfgcode.
|
|
31
|
+
new ArcError(cfgcode.LOGIN_CODE_EXPIRED, phrase.loginCodeExpired()),
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// [function]
|
|
@@ -73,7 +73,10 @@ const pollFlow = function (value, onLoginPoll) {
|
|
|
73
73
|
case "failed":
|
|
74
74
|
throw new ArcError(cfgcode.LOGIN_FAILED, phrase.loginFailed());
|
|
75
75
|
default:
|
|
76
|
-
throw new ArcError(
|
|
76
|
+
throw new ArcError(
|
|
77
|
+
cfgcode.LOGIN_CODE_EXPIRED,
|
|
78
|
+
phrase.loginCodeExpired(),
|
|
79
|
+
);
|
|
77
80
|
}
|
|
78
81
|
});
|
|
79
82
|
};
|
package/src/action/pack/tool.js
CHANGED
|
@@ -69,15 +69,26 @@ const verifyTotal = function (zip) {
|
|
|
69
69
|
|
|
70
70
|
// [function]
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// `deployDir` when the input is deploy_dir, rather than a folder --input
|
|
73
|
+
// named: a missing deploy_dir means the build hasn't written it.
|
|
74
|
+
const pack = function (
|
|
75
|
+
name,
|
|
76
|
+
game,
|
|
77
|
+
{ deployDir, inputPath, mediaPath, outputPath },
|
|
78
|
+
) {
|
|
73
79
|
const zip = new Zip(name);
|
|
74
80
|
return pathExists(inputPath)
|
|
75
81
|
.then((existsInput) => {
|
|
76
82
|
if (!existsInput) {
|
|
77
|
-
throw
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
throw deployDir
|
|
84
|
+
? new ArcError(
|
|
85
|
+
cfgcode.DEPLOY_DIR_MISSING,
|
|
86
|
+
phrase.deployDirMissing(inputPath),
|
|
87
|
+
)
|
|
88
|
+
: new ArcError(
|
|
89
|
+
cfgcode.PATH_NOT_FOUND,
|
|
90
|
+
phrase.unableToFind(inputPath),
|
|
91
|
+
);
|
|
81
92
|
}
|
|
82
93
|
zip.addDir(inputPath);
|
|
83
94
|
return zip;
|
|
@@ -105,8 +116,11 @@ const main = function ({ input, output, ...conf }) {
|
|
|
105
116
|
return given(conf).then(({ game, workflow }) => {
|
|
106
117
|
const name = game.handle ?? game.id;
|
|
107
118
|
const file = output ?? `${OUTPUT}/${name}-v${game.version}.zip`;
|
|
119
|
+
const deployDir = resolve(root, workflow.deploy_dir);
|
|
120
|
+
const inputPath = resolve(root, input ?? workflow.deploy_dir);
|
|
108
121
|
return pack(parse(file).name, game, {
|
|
109
|
-
|
|
122
|
+
deployDir: inputPath === deployDir,
|
|
123
|
+
inputPath,
|
|
110
124
|
mediaPath: resolve(root, MEDIA),
|
|
111
125
|
outputPath: resolve(root, file),
|
|
112
126
|
});
|
|
@@ -77,11 +77,28 @@ const ensureClean = function (root) {
|
|
|
77
77
|
|
|
78
78
|
// [function]
|
|
79
79
|
|
|
80
|
+
// fails with git_remote_missing when there's no origin, and with
|
|
81
|
+
// git_remote_unreachable when it's there but can't be reached.
|
|
80
82
|
const ensureRemote = function (root) {
|
|
81
|
-
return git(root, ["
|
|
82
|
-
(
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
return git(root, ["remote", "get-url", "origin"])
|
|
84
|
+
.then(
|
|
85
|
+
() => null,
|
|
86
|
+
failWith(cfgcode.GIT_REMOTE_MISSING, phrase.gitRemoteMissing()),
|
|
87
|
+
)
|
|
88
|
+
.then(() => git(root, ["ls-remote", "origin"]))
|
|
89
|
+
.then(
|
|
90
|
+
() => null,
|
|
91
|
+
// git_remote_missing passes through.
|
|
92
|
+
(err) => {
|
|
93
|
+
if (err instanceof ArcError) {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
failWith(
|
|
97
|
+
cfgcode.GIT_REMOTE_UNREACHABLE,
|
|
98
|
+
phrase.gitRemoteUnreachable(),
|
|
99
|
+
)(err);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
85
102
|
};
|
|
86
103
|
|
|
87
104
|
// [function]
|
|
@@ -168,7 +185,7 @@ const hasTagLocal = function (root, tag) {
|
|
|
168
185
|
const hasTagRemote = function (root, tag) {
|
|
169
186
|
return git(root, ["ls-remote", "--tags", "origin", `refs/tags/${tag}`]).then(
|
|
170
187
|
(stdout) => Boolean(stdout.trim()),
|
|
171
|
-
failWith(cfgcode.
|
|
188
|
+
failWith(cfgcode.GIT_REMOTE_UNREACHABLE, phrase.gitRemoteUnreachable()),
|
|
172
189
|
);
|
|
173
190
|
};
|
|
174
191
|
|
|
@@ -57,6 +57,7 @@ const ensurePublishable = function ({ handle, id, version }, handler) {
|
|
|
57
57
|
if (game === null || build === null) {
|
|
58
58
|
if (handler.onLogInfo) {
|
|
59
59
|
handler.onLogInfo({
|
|
60
|
+
code: cfgcode.WARNING.VERSION_UNCHECKED,
|
|
60
61
|
level: "warn",
|
|
61
62
|
message: phrase.versionUnchecked(version),
|
|
62
63
|
});
|
|
@@ -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 WARNING = cfgcode.WARNING;
|
|
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: WARNING.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
|
@@ -8,24 +8,26 @@
|
|
|
8
8
|
|
|
9
9
|
// the CLI's own codes.
|
|
10
10
|
const CLI = {
|
|
11
|
-
|
|
11
|
+
API_REFUSED: "api_refused",
|
|
12
|
+
API_UNREACHABLE: "api_unreachable",
|
|
12
13
|
ARGUMENT_INVALID: "argument_invalid",
|
|
13
14
|
BUILD_FAILED: "build_failed",
|
|
15
|
+
DEPLOY_DIR_MISSING: "deploy_dir_missing",
|
|
14
16
|
GAME_EXISTS: "game_exists",
|
|
15
17
|
GAME_ID_MISSING: "game_id_missing",
|
|
16
18
|
GAME_NOT_DEPLOYED: "game_not_deployed",
|
|
17
19
|
GAME_NOT_FOUND: "game_not_found",
|
|
18
|
-
|
|
20
|
+
GIT_REMOTE_MISSING: "git_remote_missing",
|
|
21
|
+
GIT_REMOTE_UNREACHABLE: "git_remote_unreachable",
|
|
19
22
|
GIT_REPO_MISSING: "git_repo_missing",
|
|
20
23
|
GIT_TAG_CREATE_FAILED: "git_tag_create_failed",
|
|
21
24
|
GIT_TAG_EXISTS: "git_tag_exists",
|
|
22
25
|
GIT_TAG_PUSH_FAILED: "git_tag_push_failed",
|
|
23
26
|
GIT_UNCOMMITTED: "git_uncommitted",
|
|
24
27
|
LOCKFILE_MISSING: "lockfile_missing",
|
|
25
|
-
|
|
28
|
+
LOGIN_CODE_EXPIRED: "login_code_expired",
|
|
26
29
|
LOGIN_FAILED: "login_failed",
|
|
27
30
|
MANIFEST_MISSING: "manifest_missing",
|
|
28
|
-
NETWORK_ERROR: "network_error",
|
|
29
31
|
NOT_LOGGED_IN: "not_logged_in",
|
|
30
32
|
PACKAGE_FILE_TOO_LARGE: "package_file_too_large",
|
|
31
33
|
PACKAGE_INDEX_MISSING: "package_index_missing",
|
|
@@ -48,24 +50,26 @@ const CLI = {
|
|
|
48
50
|
// thing wherever it appears: GAME_UNAVAILABLE, INTERNAL, REPO_NOT_CONNECTED,
|
|
49
51
|
// and VERSION_EXISTS are the CLI's too.
|
|
50
52
|
const RELEASE = {
|
|
51
|
-
|
|
52
|
-
EXPIRED: "expired",
|
|
53
|
+
BUILD_RATE_LIMITED: "build_rate_limited",
|
|
53
54
|
GAME_LIMIT_REACHED: "game_limit_reached",
|
|
54
55
|
GAME_MISMATCH: "game_mismatch",
|
|
55
56
|
GAME_UNAVAILABLE: "game_unavailable",
|
|
57
|
+
GITHUB_APP_NOT_INSTALLED: "github_app_not_installed",
|
|
56
58
|
INTERNAL: "internal",
|
|
57
59
|
MANIFEST_INVALID: "manifest_invalid",
|
|
58
|
-
|
|
60
|
+
RELEASE_EXPIRED: "release_expired",
|
|
59
61
|
REPO_NOT_CONNECTED: "repo_not_connected",
|
|
60
62
|
VERSION_EXISTS: "version_exists",
|
|
63
|
+
VERSION_MISMATCH: "version_mismatch",
|
|
61
64
|
ZIP_INVALID: "zip_invalid",
|
|
65
|
+
ZIP_MISSING: "zip_missing",
|
|
62
66
|
ZIP_TOO_LARGE: "zip_too_large",
|
|
63
67
|
};
|
|
64
68
|
// the Arcadible check's own codes on GitHub, for a `git` release that fails
|
|
65
|
-
// before there's a release (the queue publisher
|
|
69
|
+
// before there's a release (the queue publisher and subscriber Lambdas). `arc deploy git`
|
|
66
70
|
// checks for workflow_missing before it pushes.
|
|
67
71
|
const GITHUB = {
|
|
68
|
-
|
|
72
|
+
GIT_TAG_INVALID: "git_tag_invalid",
|
|
69
73
|
WORKFLOW_MISSING: "workflow_missing",
|
|
70
74
|
};
|
|
71
75
|
// an `arc validate` issue's codes, named for the rule it breaks; the field
|
|
@@ -92,6 +96,14 @@ const ISSUE = {
|
|
|
92
96
|
UUID_INVALID: "uuid_invalid",
|
|
93
97
|
VERSION_INVALID: "version_invalid",
|
|
94
98
|
};
|
|
99
|
+
// a warning's codes: what's reported without failing, in an issue's shape.
|
|
100
|
+
// they follow the same rules, and share the Errors page, so no warning code
|
|
101
|
+
// is also an error or issue code.
|
|
102
|
+
const WARNING = {
|
|
103
|
+
DEPLOY_DIR_NOT_BUILT: "deploy_dir_not_built",
|
|
104
|
+
FIELD_UNKNOWN: "field_unknown",
|
|
105
|
+
VERSION_UNCHECKED: "version_unchecked",
|
|
106
|
+
};
|
|
95
107
|
// every error code, for checking one exists.
|
|
96
108
|
const ALL = new Set([
|
|
97
109
|
...Object.values(CLI),
|
|
@@ -100,6 +112,8 @@ const ALL = new Set([
|
|
|
100
112
|
]);
|
|
101
113
|
// every issue code.
|
|
102
114
|
const ISSUES = new Set(Object.values(ISSUE));
|
|
115
|
+
// every warning code.
|
|
116
|
+
const WARNINGS = new Set(Object.values(WARNING));
|
|
103
117
|
|
|
104
118
|
// [export]
|
|
105
119
|
|
|
@@ -112,4 +126,6 @@ module.exports = {
|
|
|
112
126
|
ISSUE,
|
|
113
127
|
ISSUES,
|
|
114
128
|
RELEASE,
|
|
129
|
+
WARNING,
|
|
130
|
+
WARNINGS,
|
|
115
131
|
};
|
package/src/phrase.js
CHANGED
|
@@ -101,6 +101,15 @@ const cmdValidate = function () {
|
|
|
101
101
|
|
|
102
102
|
// [function]
|
|
103
103
|
|
|
104
|
+
const deployDirMissing = function (dir) {
|
|
105
|
+
return blocks(
|
|
106
|
+
`Unable to find deploy_dir: ${dir}`,
|
|
107
|
+
"Run `arc build`, or set deploy_dir to the folder your build writes.",
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// [function]
|
|
112
|
+
|
|
104
113
|
const docAttrInvalid = function (tag, attr, value) {
|
|
105
114
|
return `Value '${value}' is not valid for the '${attr}' attribute on the '${tag}' tag.`;
|
|
106
115
|
};
|
|
@@ -207,8 +216,17 @@ const gitChangesPresent = function () {
|
|
|
207
216
|
|
|
208
217
|
const gitRemoteMissing = function () {
|
|
209
218
|
return blocks(
|
|
210
|
-
"
|
|
211
|
-
"
|
|
219
|
+
"No Git remote 'origin'.",
|
|
220
|
+
"Add the game's GitHub repository as origin.",
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// [function]
|
|
225
|
+
|
|
226
|
+
const gitRemoteUnreachable = function () {
|
|
227
|
+
return blocks(
|
|
228
|
+
"Unable to reach Git remote 'origin'.",
|
|
229
|
+
"Check your connection, and that you can access it.",
|
|
212
230
|
);
|
|
213
231
|
};
|
|
214
232
|
|
|
@@ -216,7 +234,7 @@ const gitRemoteMissing = function () {
|
|
|
216
234
|
|
|
217
235
|
const noManifest = function (file, dir, found) {
|
|
218
236
|
return blocks(
|
|
219
|
-
dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}
|
|
237
|
+
dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}.`,
|
|
220
238
|
found
|
|
221
239
|
? `The project is in \`${found}\`: run arc there, or pass \`-C ${found}\`.`
|
|
222
240
|
: "Run arc in the project's directory, or pass -C, or start a project with `arc init`.",
|
|
@@ -337,6 +355,17 @@ const timeoutInvalid = function () {
|
|
|
337
355
|
|
|
338
356
|
// [function]
|
|
339
357
|
|
|
358
|
+
// issues in what the build wrote, such as its art, once arc deploy has
|
|
359
|
+
// parsed the project again.
|
|
360
|
+
const issuesAfterBuild = function (count) {
|
|
361
|
+
return blocks(
|
|
362
|
+
`${count} ${count === 1 ? "issue" : "issues"} found after the build.`,
|
|
363
|
+
`The build writes files the manifest reads. Run ${cmdValidate()} for details.`,
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// [function]
|
|
368
|
+
|
|
340
369
|
const issuesFound = function (count) {
|
|
341
370
|
return blocks(
|
|
342
371
|
`${count} ${count === 1 ? "issue" : "issues"} found.`,
|
|
@@ -346,8 +375,8 @@ const issuesFound = function (count) {
|
|
|
346
375
|
|
|
347
376
|
// [function]
|
|
348
377
|
|
|
349
|
-
const
|
|
350
|
-
return "Login
|
|
378
|
+
const loginCodeExpired = function () {
|
|
379
|
+
return "Login code expired.";
|
|
351
380
|
};
|
|
352
381
|
|
|
353
382
|
// [function]
|
|
@@ -458,10 +487,9 @@ const packageUncompressedTooLarge = function (size) {
|
|
|
458
487
|
// and where to see it.
|
|
459
488
|
// `studio` is the game's builds in Studio, for its owner; anyone else can't
|
|
460
489
|
// open it.
|
|
461
|
-
const releaseFailed = function ({ message
|
|
490
|
+
const releaseFailed = function ({ message }, studio) {
|
|
462
491
|
return blocks(
|
|
463
492
|
(message ?? "").split(/(?<=\.)\s+/u).join("\n"),
|
|
464
|
-
`Reason: ${reason}.`,
|
|
465
493
|
...(studio ? [[`${TITLE} Studio:`, studio]] : []),
|
|
466
494
|
);
|
|
467
495
|
};
|
|
@@ -756,6 +784,7 @@ module.exports = {
|
|
|
756
784
|
buildSubmitted,
|
|
757
785
|
cmdInit,
|
|
758
786
|
cmdInspect,
|
|
787
|
+
deployDirMissing,
|
|
759
788
|
docAttrInvalid,
|
|
760
789
|
docAttrUnallowed,
|
|
761
790
|
docCommentUnallowed,
|
|
@@ -772,6 +801,7 @@ module.exports = {
|
|
|
772
801
|
gameUnavailable,
|
|
773
802
|
gitChangesPresent,
|
|
774
803
|
gitRemoteMissing,
|
|
804
|
+
gitRemoteUnreachable,
|
|
775
805
|
gitRepoMissing,
|
|
776
806
|
gitTagCreateFailed,
|
|
777
807
|
gitTagPushFailed,
|
|
@@ -786,8 +816,9 @@ module.exports = {
|
|
|
786
816
|
imageUnsupported,
|
|
787
817
|
imageWidthMax,
|
|
788
818
|
imageWidthMin,
|
|
819
|
+
issuesAfterBuild,
|
|
789
820
|
issuesFound,
|
|
790
|
-
|
|
821
|
+
loginCodeExpired,
|
|
791
822
|
loginFailed,
|
|
792
823
|
noGameId,
|
|
793
824
|
noLockFile,
|
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/error.js
CHANGED
|
@@ -32,14 +32,15 @@ const ArcError = class extends Error {
|
|
|
32
32
|
|
|
33
33
|
// [function]
|
|
34
34
|
|
|
35
|
-
// the code a failure reports: its own,
|
|
36
|
-
// refused, or internal for
|
|
35
|
+
// the code a failure reports: its own, api_refused for a request the API
|
|
36
|
+
// refused, or internal for a failure on Arcadible's side (a 5xx) and for
|
|
37
|
+
// anything else, which is a bug in the CLI.
|
|
37
38
|
const codeOf = function (ex) {
|
|
38
39
|
if (ex instanceof ArcError) {
|
|
39
40
|
return ex.code;
|
|
40
41
|
}
|
|
41
|
-
if (ex instanceof HttpError) {
|
|
42
|
-
return cfgcode.
|
|
42
|
+
if (ex instanceof HttpError && !(ex.status >= 500)) {
|
|
43
|
+
return cfgcode.API_REFUSED;
|
|
43
44
|
}
|
|
44
45
|
return cfgcode.INTERNAL;
|
|
45
46
|
};
|
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]
|
|
@@ -65,7 +66,7 @@ const print = function (data, { key, nl, toml } = {}) {
|
|
|
65
66
|
// release that failed. `nl` puts a blank line before the heading, after
|
|
66
67
|
// output the command has already printed, such as arc login's link.
|
|
67
68
|
// `code` is a failed release's reason, which the API may add to, so it isn't
|
|
68
|
-
// checked
|
|
69
|
+
// checked.
|
|
69
70
|
const abort = function (
|
|
70
71
|
heading,
|
|
71
72
|
ex,
|
|
@@ -73,12 +74,15 @@ const abort = function (
|
|
|
73
74
|
) {
|
|
74
75
|
const message = ex.message;
|
|
75
76
|
const status = ex.status;
|
|
77
|
+
const shown = code ?? codeOf(ex);
|
|
78
|
+
const link = `${cfgurl.ERRORS}#${shown}`;
|
|
76
79
|
if (json) {
|
|
77
80
|
print({
|
|
78
81
|
...ex.data,
|
|
79
82
|
...data,
|
|
80
83
|
error: {
|
|
81
|
-
code:
|
|
84
|
+
code: shown,
|
|
85
|
+
link,
|
|
82
86
|
message,
|
|
83
87
|
...(status ? { status } : {}),
|
|
84
88
|
},
|
|
@@ -96,16 +100,38 @@ const abort = function (
|
|
|
96
100
|
log.hd(heading);
|
|
97
101
|
}
|
|
98
102
|
log.pd(message);
|
|
99
|
-
|
|
100
|
-
log.nlpd([
|
|
101
|
-
phrase.errorCode(codeOf(ex)),
|
|
102
|
-
chalk.cyan.underline(`${cfgurl.ERRORS}#${codeOf(ex)}`),
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
103
|
+
log.nlpd([phrase.errorCode(shown), chalk.cyan.underline(link)]);
|
|
105
104
|
}
|
|
106
105
|
process.exit(1);
|
|
107
106
|
};
|
|
108
107
|
|
|
108
|
+
// [function]
|
|
109
|
+
|
|
110
|
+
// arc validate's warnings, such as an unknown field, on stderr in yellow,
|
|
111
|
+
// before a command that validates goes on. Left out with `--json` and
|
|
112
|
+
// `--quiet`; arc validate --json lists them.
|
|
113
|
+
const warn = function (warnings, { json, quiet } = {}) {
|
|
114
|
+
if (json || quiet) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
warnings.forEach((each) => log.pw(`⚠ ${schema.print(each)}`));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// [function]
|
|
121
|
+
|
|
122
|
+
// a warning of the command's own, in an issue's shape, as --json lists it
|
|
123
|
+
// under `warnings` beside arc validate's: its code, and the link to it on
|
|
124
|
+
// the Errors page. `path` is the field it's about, if any.
|
|
125
|
+
const warning = function (code, message, path = null) {
|
|
126
|
+
return {
|
|
127
|
+
code,
|
|
128
|
+
details: [],
|
|
129
|
+
link: `${cfgurl.ERRORS}#${code}`,
|
|
130
|
+
message,
|
|
131
|
+
path,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
109
135
|
// [export]
|
|
110
136
|
|
|
111
|
-
module.exports = { abort, print, printJson, printToml };
|
|
137
|
+
module.exports = { abort, print, printJson, printToml, warn, warning };
|
package/src/util/request.js
CHANGED
|
@@ -53,7 +53,7 @@ const parse = function (resp) {
|
|
|
53
53
|
|
|
54
54
|
// sends a request to a full URL. `headers` are added to the client's own, and
|
|
55
55
|
// a form is sent as JSON. A request that gets no response, such as when
|
|
56
|
-
// offline, rejects as
|
|
56
|
+
// offline, rejects as api_unreachable.
|
|
57
57
|
const send = function (method, url, form, headers = {}) {
|
|
58
58
|
const all = {
|
|
59
59
|
[`X-${CLIENT}-Client-Id`]: ID,
|
|
@@ -66,7 +66,7 @@ const send = function (method, url, form, headers = {}) {
|
|
|
66
66
|
headers: all,
|
|
67
67
|
method,
|
|
68
68
|
}).then(parse, (ex) => {
|
|
69
|
-
throw new ArcError(cfgcode.
|
|
69
|
+
throw new ArcError(cfgcode.API_UNREACHABLE, phrase.unableToReach(), {
|
|
70
70
|
cause: ex,
|
|
71
71
|
});
|
|
72
72
|
});
|
package/src/util/s3.js
CHANGED
|
@@ -32,7 +32,7 @@ const requestOpts = function (size) {
|
|
|
32
32
|
// [function]
|
|
33
33
|
|
|
34
34
|
// puts the file to the upload link, and resolves once S3 accepts it. A
|
|
35
|
-
// request that gets no response rejects as
|
|
35
|
+
// request that gets no response rejects as api_unreachable; a file that can't
|
|
36
36
|
// be read rejects with its own error.
|
|
37
37
|
const upload = function (url, { path, size }, onProgress) {
|
|
38
38
|
return new Promise((resolve, reject) => {
|
|
@@ -49,7 +49,7 @@ const upload = function (url, { path, size }, onProgress) {
|
|
|
49
49
|
req.on("error", (ex) => {
|
|
50
50
|
reject(
|
|
51
51
|
readError ??
|
|
52
|
-
new ArcError(cfgcode.
|
|
52
|
+
new ArcError(cfgcode.API_UNREACHABLE, phrase.unableToReach(), {
|
|
53
53
|
cause: ex,
|
|
54
54
|
}),
|
|
55
55
|
);
|
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
|
+
};
|