arcadible 0.1.33 → 0.1.35
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/bin/arc-login.js +16 -7
- package/bin/arc-whoami.js +2 -2
- package/package.json +1 -1
- package/src/account.js +1 -2
- package/src/action/deploy/wait.js +27 -13
- package/src/action/predeploy/git.js +14 -0
- package/src/action/predeploy/tool-git.js +41 -8
- package/src/api.js +14 -0
- package/src/cli/predeploy.js +1 -1
- package/src/config/code.js +3 -2
- package/src/parser/env.js +7 -1
- package/src/phrase.js +33 -7
- package/src/schema/game.js +17 -8
- package/src/util/output.js +8 -3
package/bin/arc-login.js
CHANGED
|
@@ -16,6 +16,7 @@ const cfgproduct = require("../src/config/product");
|
|
|
16
16
|
const error = require("../src/util/error");
|
|
17
17
|
const log = require("../src/util/log");
|
|
18
18
|
const login = require("../src/cli/login");
|
|
19
|
+
const output = require("../src/util/output");
|
|
19
20
|
const phrase = require("../src/phrase");
|
|
20
21
|
const rc = require("../src/util/rc");
|
|
21
22
|
const shell = require("../src/util/shell");
|
|
@@ -23,6 +24,7 @@ const spinner = require("../src/util/spinner");
|
|
|
23
24
|
|
|
24
25
|
// [use]
|
|
25
26
|
|
|
27
|
+
const abort = output.abort;
|
|
26
28
|
const ArcError = error.ArcError;
|
|
27
29
|
const findRc = rc.findRc;
|
|
28
30
|
const launch = shell.launch;
|
|
@@ -70,10 +72,13 @@ const onLoginEnter = function (fn) {
|
|
|
70
72
|
|
|
71
73
|
// [function]
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
// `shown` is set once the link is printed, so a failure after it starts
|
|
76
|
+
// after a blank line.
|
|
77
|
+
const handler = function ({ browser, qrcode: qr, shown, sw }) {
|
|
74
78
|
return {
|
|
75
79
|
onLoginCreated({ code: dac, secret, url }) {
|
|
76
80
|
sw.stop(true);
|
|
81
|
+
shown.link = true;
|
|
77
82
|
const link = `${url}/${secret}`;
|
|
78
83
|
log.p("Logging in...");
|
|
79
84
|
qrcode.generate(link, { small: true }, (code) => {
|
|
@@ -82,7 +87,8 @@ const handler = function ({ browser, qrcode: qr, sw }) {
|
|
|
82
87
|
}
|
|
83
88
|
log.nlp(url);
|
|
84
89
|
log.h(dac.toUpperCase());
|
|
85
|
-
|
|
90
|
+
// a key press can only be read from a terminal.
|
|
91
|
+
if (browser && process.stdin.isTTY) {
|
|
86
92
|
log.nlp("Press Enter to open the URL.");
|
|
87
93
|
onLoginEnter(() => {
|
|
88
94
|
launch(link);
|
|
@@ -96,10 +102,16 @@ const handler = function ({ browser, qrcode: qr, sw }) {
|
|
|
96
102
|
// [function]
|
|
97
103
|
|
|
98
104
|
const start = function (argv) {
|
|
105
|
+
const shown = { link: false };
|
|
99
106
|
const sw = new SpinnerWheel("Logging in... %s");
|
|
100
107
|
sw.start();
|
|
101
108
|
return login({
|
|
102
|
-
handler: handler({
|
|
109
|
+
handler: handler({
|
|
110
|
+
browser: argv.browser,
|
|
111
|
+
qrcode: argv.qrcode,
|
|
112
|
+
shown,
|
|
113
|
+
sw,
|
|
114
|
+
}),
|
|
103
115
|
})
|
|
104
116
|
.then((current) => {
|
|
105
117
|
const user = current?.user;
|
|
@@ -111,11 +123,8 @@ const start = function (argv) {
|
|
|
111
123
|
log.result("Logged in.", [user.display_name], { nl: true });
|
|
112
124
|
})
|
|
113
125
|
.catch((ex) => {
|
|
114
|
-
sw.stop(true);
|
|
115
126
|
process.stdin.pause();
|
|
116
|
-
|
|
117
|
-
log.pd(ex.message);
|
|
118
|
-
process.exit(1);
|
|
127
|
+
abort("Login aborted.", ex, { nl: shown.link, sw });
|
|
119
128
|
});
|
|
120
129
|
};
|
|
121
130
|
|
package/bin/arc-whoami.js
CHANGED
|
@@ -8,8 +8,8 @@ const yargs = require("yargs/yargs");
|
|
|
8
8
|
|
|
9
9
|
// [import]
|
|
10
10
|
|
|
11
|
-
const account = require("../src/account");
|
|
12
11
|
const args = require("../src/util/argv");
|
|
12
|
+
const cfgcode = require("../src/config/code");
|
|
13
13
|
const cfgpackage = require("../src/config/package");
|
|
14
14
|
const cfgproduct = require("../src/config/product");
|
|
15
15
|
const log = require("../src/util/log");
|
|
@@ -76,7 +76,7 @@ const start = function (argv) {
|
|
|
76
76
|
.catch((ex) => {
|
|
77
77
|
const message = ex.message;
|
|
78
78
|
// a session that can't be refreshed is no session: say so, and why.
|
|
79
|
-
if (ex.code ===
|
|
79
|
+
if (ex.code === cfgcode.SESSION_EXPIRED) {
|
|
80
80
|
if (json) {
|
|
81
81
|
print({ user: null });
|
|
82
82
|
} else {
|
package/package.json
CHANGED
package/src/account.js
CHANGED
|
@@ -40,7 +40,6 @@ const FILE_MODE = 0o600;
|
|
|
40
40
|
|
|
41
41
|
// the statuses that mean the refresh token itself was refused.
|
|
42
42
|
const REJECTED = [400, 401, 403];
|
|
43
|
-
const SESSION_EXPIRED = cfgcode.SESSION_EXPIRED;
|
|
44
43
|
|
|
45
44
|
// [declare]
|
|
46
45
|
|
|
@@ -154,4 +153,4 @@ const remove = function () {
|
|
|
154
153
|
|
|
155
154
|
// [export]
|
|
156
155
|
|
|
157
|
-
module.exports = {
|
|
156
|
+
module.exports = { get, remove, save };
|
|
@@ -19,7 +19,7 @@ const sleep = delay.sleep;
|
|
|
19
19
|
const INTERVAL = time.s(2);
|
|
20
20
|
const FINAL = new Set(["failed", "published"]);
|
|
21
21
|
const SELECT =
|
|
22
|
-
"id,status,reason,message,build,source,version,channel,created_at";
|
|
22
|
+
"id,game,status,reason,message,build,source,version,channel,created_at";
|
|
23
23
|
// a git release is found by version among the newest few; one from before
|
|
24
24
|
// this deploy (a tag deleted and pushed again) is left out, allowing for
|
|
25
25
|
// the machine's clock being off.
|
|
@@ -67,18 +67,26 @@ const findDirect = function (id, headers) {
|
|
|
67
67
|
// [function]
|
|
68
68
|
|
|
69
69
|
// a git deploy's release: GitHub's, for this version, made since the tag was
|
|
70
|
-
// pushed. Null until GitHub Actions has built it.
|
|
71
|
-
|
|
70
|
+
// pushed. Null until GitHub Actions has built it. It's looked for among the
|
|
71
|
+
// account's releases, which include one with no game: a repository that
|
|
72
|
+
// isn't connected fails that way (repo_not_connected). A token's account
|
|
73
|
+
// isn't known, so it looks among the game's.
|
|
74
|
+
const findGit = function ({ account, game, headers, since, version }) {
|
|
75
|
+
const newest = account
|
|
76
|
+
? () => api.accountReleases(account, { limit: 20, select: SELECT }, headers)
|
|
77
|
+
: () =>
|
|
78
|
+
api.gameReleases(game, { limit: 10, select: SELECT, version }, headers);
|
|
72
79
|
return () =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
newest().then(
|
|
81
|
+
(rows) =>
|
|
82
|
+
(rows ?? []).find(
|
|
83
|
+
(r) =>
|
|
84
|
+
r.source === "github" &&
|
|
85
|
+
r.version === version &&
|
|
86
|
+
(r.game === game || r.game === null) &&
|
|
87
|
+
Date.parse(r.created_at) >= since - SKEW,
|
|
88
|
+
) ?? null,
|
|
89
|
+
);
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
// [function]
|
|
@@ -128,7 +136,13 @@ const main = function (
|
|
|
128
136
|
const headers = api.auth(current?.session?.access_token);
|
|
129
137
|
const game = deployed.game;
|
|
130
138
|
const find = isGit
|
|
131
|
-
? findGit(
|
|
139
|
+
? findGit({
|
|
140
|
+
account: current?.user?.id ?? null,
|
|
141
|
+
game,
|
|
142
|
+
headers,
|
|
143
|
+
since,
|
|
144
|
+
version: deployed.version.to,
|
|
145
|
+
})
|
|
132
146
|
: findDirect(deployed.release, headers);
|
|
133
147
|
return Promise.all([
|
|
134
148
|
poll(find, timeout, onRelease),
|
|
@@ -20,6 +20,8 @@ const execFile = cp.execFile;
|
|
|
20
20
|
|
|
21
21
|
const HANDLE = cfgproduct.handle;
|
|
22
22
|
const PREFIX = `${HANDLE}/release/v`;
|
|
23
|
+
// the release workflow, which builds the release on GitHub Actions.
|
|
24
|
+
const WORKFLOW = ".github/workflows/arcadible-release.yml";
|
|
23
25
|
|
|
24
26
|
// [function]
|
|
25
27
|
|
|
@@ -86,6 +88,17 @@ const ensureRepo = function (root) {
|
|
|
86
88
|
|
|
87
89
|
// [function]
|
|
88
90
|
|
|
91
|
+
// fails when HEAD has no release workflow, which Arcadible checks for when the
|
|
92
|
+
// tag arrives (workflow_missing).
|
|
93
|
+
const ensureWorkflow = function (root) {
|
|
94
|
+
return git(root, ["cat-file", "-e", `HEAD:${WORKFLOW}`]).then(
|
|
95
|
+
() => null,
|
|
96
|
+
failWith(cfgcode.WORKFLOW_MISSING, phrase.workflowMissing(WORKFLOW)),
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// [function]
|
|
101
|
+
|
|
89
102
|
const hasTagLocal = function (root, tag) {
|
|
90
103
|
return git(root, ["tag", "--list", tag]).then((stdout) =>
|
|
91
104
|
Boolean(stdout.trim()),
|
|
@@ -125,6 +138,7 @@ module.exports = {
|
|
|
125
138
|
ensureRemote,
|
|
126
139
|
ensureRepo,
|
|
127
140
|
ensureTagFree,
|
|
141
|
+
ensureWorkflow,
|
|
128
142
|
failWith,
|
|
129
143
|
git,
|
|
130
144
|
tagOf,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const api = require("../../api");
|
|
6
6
|
const cfgcode = require("../../config/code");
|
|
7
|
+
const cfguri = require("../../config/uri");
|
|
7
8
|
const error = require("../../util/error");
|
|
8
9
|
const git = require("./git");
|
|
9
10
|
const given = require("../../parser/given");
|
|
@@ -19,6 +20,7 @@ const ensureLockFile = pm.ensureLockFile;
|
|
|
19
20
|
const ensureRemote = git.ensureRemote;
|
|
20
21
|
const ensureRepo = git.ensureRepo;
|
|
21
22
|
const ensureTagFree = git.ensureTagFree;
|
|
23
|
+
const ensureWorkflow = git.ensureWorkflow;
|
|
22
24
|
|
|
23
25
|
// [function]
|
|
24
26
|
|
|
@@ -68,35 +70,66 @@ const ensurePublishable = function ({ handle, id, version }, handler) {
|
|
|
68
70
|
// [function]
|
|
69
71
|
|
|
70
72
|
// the project's checks: a lockfile when there's a package.json, and a clean
|
|
71
|
-
// repository with a reachable origin.
|
|
73
|
+
// repository with the release workflow and a reachable origin.
|
|
72
74
|
const ensureProject = function (root) {
|
|
73
75
|
return ensureLockFile(root)
|
|
74
76
|
.then(() => ensureRepo(root))
|
|
75
77
|
.then(() => ensureClean(root))
|
|
78
|
+
.then(() => ensureWorkflow(root))
|
|
76
79
|
.then(() => ensureRemote(root));
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
// [function]
|
|
80
83
|
|
|
81
|
-
// the
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
84
|
+
// fails when the game has no repository connected, which Arcadible would
|
|
85
|
+
// refuse (repo_not_connected) after GitHub Actions had built it. Only the
|
|
86
|
+
// game's owner can see its repository, so it's checked only when they're
|
|
87
|
+
// logged in, and skipped when Arcadible can't be reached.
|
|
88
|
+
const ensureConnected = function (current, { handle, id }, owner) {
|
|
89
|
+
if (!owner || current?.token || current?.user?.id !== owner) {
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
const headers = api.auth(current.session?.access_token);
|
|
93
|
+
return api.gameRepos(id, { select: "id" }, headers).then(
|
|
94
|
+
(repos) => {
|
|
95
|
+
if (!repos?.length) {
|
|
96
|
+
throw new ArcError(
|
|
97
|
+
cfgcode.REPO_NOT_CONNECTED,
|
|
98
|
+
phrase.gameRepoMissing(
|
|
99
|
+
handle ?? id,
|
|
100
|
+
`${cfguri.HOST_HTTP_STUDIO}/games/${id}/repository`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
() => null,
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// [function]
|
|
110
|
+
|
|
111
|
+
// the release's checks: its tag is free, Arcadible would publish it, and its
|
|
112
|
+
// repository is connected. resolves with the version, and the game's owner,
|
|
113
|
+
// who alone can wait for the release.
|
|
114
|
+
const ensureRelease = function (root, current, game, handler) {
|
|
85
115
|
return ensureTagFree(root, game.version)
|
|
86
116
|
.then(() => ensurePublishable(game, handler))
|
|
87
|
-
.then((owner) =>
|
|
117
|
+
.then((owner) =>
|
|
118
|
+
Promise.all([owner, ensureConnected(current, game, owner)]),
|
|
119
|
+
)
|
|
120
|
+
.then(([owner]) => ({ owner, to: game.version }));
|
|
88
121
|
};
|
|
89
122
|
|
|
90
123
|
// [function]
|
|
91
124
|
|
|
92
|
-
const main = function (conf, { onPredeployBegin, ...handler }) {
|
|
125
|
+
const main = function (current, conf, { onPredeployBegin, ...handler }) {
|
|
93
126
|
const root = conf.root;
|
|
94
127
|
if (onPredeployBegin) {
|
|
95
128
|
onPredeployBegin();
|
|
96
129
|
}
|
|
97
130
|
return given(conf)
|
|
98
131
|
.then(({ game }) => Promise.all([game, ensureProject(root)]))
|
|
99
|
-
.then(([game]) => ensureRelease(root, game, handler));
|
|
132
|
+
.then(([game]) => ensureRelease(root, current, game, handler));
|
|
100
133
|
};
|
|
101
134
|
|
|
102
135
|
// [export]
|
package/src/api.js
CHANGED
|
@@ -92,6 +92,18 @@ const release = function (id, params, headers) {
|
|
|
92
92
|
|
|
93
93
|
// [function]
|
|
94
94
|
|
|
95
|
+
const accountReleases = function (id, params, headers) {
|
|
96
|
+
return request.get(endpoint(`/accounts/${id}/releases`, params), headers);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// [function]
|
|
100
|
+
|
|
101
|
+
const gameRepos = function (id, params, headers) {
|
|
102
|
+
return request.get(endpoint(`/games/${id}/repos`, params), headers);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// [function]
|
|
106
|
+
|
|
95
107
|
const gameReleases = function (id, params, headers) {
|
|
96
108
|
return request.get(endpoint(`/games/${id}/releases`, params), headers);
|
|
97
109
|
};
|
|
@@ -99,6 +111,7 @@ const gameReleases = function (id, params, headers) {
|
|
|
99
111
|
// [export]
|
|
100
112
|
|
|
101
113
|
module.exports = {
|
|
114
|
+
accountReleases,
|
|
102
115
|
auth,
|
|
103
116
|
discoverGame,
|
|
104
117
|
discoverGameBuild,
|
|
@@ -106,6 +119,7 @@ module.exports = {
|
|
|
106
119
|
flow,
|
|
107
120
|
gameBuild,
|
|
108
121
|
gameReleases,
|
|
122
|
+
gameRepos,
|
|
109
123
|
postAuthRefresh,
|
|
110
124
|
postFlow,
|
|
111
125
|
postUpload,
|
package/src/cli/predeploy.js
CHANGED
|
@@ -9,7 +9,7 @@ const predeployToolGit = require("../action/predeploy/tool-git");
|
|
|
9
9
|
|
|
10
10
|
const main = function ({ current, git, handler, ...conf }) {
|
|
11
11
|
return git
|
|
12
|
-
? predeployToolGit(conf, handler)
|
|
12
|
+
? predeployToolGit(current, conf, handler)
|
|
13
13
|
: predeployTool(current, conf, handler);
|
|
14
14
|
};
|
|
15
15
|
|
package/src/config/code.js
CHANGED
|
@@ -59,8 +59,8 @@ const RELEASE = {
|
|
|
59
59
|
ZIP_TOO_LARGE: "zip_too_large",
|
|
60
60
|
};
|
|
61
61
|
// the Arcadible check's own codes on GitHub, for a `git` release that fails
|
|
62
|
-
// before there's a release (the queue publisher Lambda).
|
|
63
|
-
//
|
|
62
|
+
// before there's a release (the queue publisher Lambda). `arc deploy git`
|
|
63
|
+
// checks for workflow_missing before it pushes.
|
|
64
64
|
const GITHUB = {
|
|
65
65
|
TAG_INVALID: "tag_invalid",
|
|
66
66
|
WORKFLOW_MISSING: "workflow_missing",
|
|
@@ -104,6 +104,7 @@ module.exports = {
|
|
|
104
104
|
ALL,
|
|
105
105
|
...RELEASE,
|
|
106
106
|
...CLI,
|
|
107
|
+
...GITHUB,
|
|
107
108
|
GITHUB,
|
|
108
109
|
ISSUE,
|
|
109
110
|
ISSUES,
|
package/src/parser/env.js
CHANGED
|
@@ -25,6 +25,8 @@ const readFile = fs.readFile;
|
|
|
25
25
|
|
|
26
26
|
// [declare]
|
|
27
27
|
|
|
28
|
+
// the build a package.json build script stands for.
|
|
29
|
+
const NPM_RUN_BUILD = "npm run build";
|
|
28
30
|
const PACKAGE = "package.json";
|
|
29
31
|
|
|
30
32
|
// [function]
|
|
@@ -51,10 +53,14 @@ const parseJson = function (buf) {
|
|
|
51
53
|
|
|
52
54
|
// [function]
|
|
53
55
|
|
|
56
|
+
// package.json's version, and its build script as `npm run build`: run
|
|
57
|
+
// through npm, the script finds the project's own tools on the PATH
|
|
58
|
+
// (node_modules/.bin), and its prebuild and postbuild run, as they do
|
|
59
|
+
// locally. npm runs the scripts of a pnpm or Yarn project too.
|
|
54
60
|
const extract = function (conf) {
|
|
55
61
|
/* eslint-disable camelcase */
|
|
56
62
|
const game = { version: conf.version };
|
|
57
|
-
const workflow =
|
|
63
|
+
const workflow = conf.scripts?.build ? { build: NPM_RUN_BUILD } : {};
|
|
58
64
|
/* eslint-enable camelcase */
|
|
59
65
|
return deepCompact({ game, workflow }) ?? {};
|
|
60
66
|
};
|
package/src/phrase.js
CHANGED
|
@@ -171,6 +171,15 @@ const gameNotDeployed = function (game) {
|
|
|
171
171
|
|
|
172
172
|
// [function]
|
|
173
173
|
|
|
174
|
+
const gameRepoMissing = function (game, url) {
|
|
175
|
+
return blocks(`${game} isn't connected to a GitHub repository.`, [
|
|
176
|
+
`Connect this repository in ${TITLE} Studio, on the game's Repository tab:`,
|
|
177
|
+
url,
|
|
178
|
+
]);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// [function]
|
|
182
|
+
|
|
174
183
|
const gameNotFound = function (idOrHandle) {
|
|
175
184
|
return `Game not found: ${idOrHandle}`;
|
|
176
185
|
};
|
|
@@ -465,14 +474,20 @@ const releaseTimedOut = function (
|
|
|
465
474
|
seconds,
|
|
466
475
|
{ actions, found, git, studio },
|
|
467
476
|
) {
|
|
477
|
+
const head = `Waited ${waited(seconds)} for v${version}.`;
|
|
478
|
+
if (found || !git) {
|
|
479
|
+
return blocks(head, [
|
|
480
|
+
`It may still finish. See it in ${TITLE} Studio:`,
|
|
481
|
+
studio,
|
|
482
|
+
]);
|
|
483
|
+
}
|
|
468
484
|
return blocks(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
],
|
|
485
|
+
head,
|
|
486
|
+
[
|
|
487
|
+
"GitHub Actions hasn't released it yet, or its workflow failed:",
|
|
488
|
+
actions ?? "See the repository's Actions tab.",
|
|
489
|
+
],
|
|
490
|
+
`If the workflow passed, the ${TITLE} check on the tagged commit says why.`,
|
|
476
491
|
);
|
|
477
492
|
};
|
|
478
493
|
|
|
@@ -609,6 +624,15 @@ const uploadRateLimited = function (version) {
|
|
|
609
624
|
|
|
610
625
|
// [function]
|
|
611
626
|
|
|
627
|
+
const workflowMissing = function (file) {
|
|
628
|
+
return blocks(
|
|
629
|
+
`No release workflow found: ${file}`,
|
|
630
|
+
"Add it from your starter, and commit it.",
|
|
631
|
+
);
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// [function]
|
|
635
|
+
|
|
612
636
|
const versionPrerelease = function (version) {
|
|
613
637
|
return `Prerelease v${version}`;
|
|
614
638
|
};
|
|
@@ -729,6 +753,7 @@ module.exports = {
|
|
|
729
753
|
gameExists,
|
|
730
754
|
gameNotDeployed,
|
|
731
755
|
gameNotFound,
|
|
756
|
+
gameRepoMissing,
|
|
732
757
|
gameUnavailable,
|
|
733
758
|
gitChangesPresent,
|
|
734
759
|
gitRemoteMissing,
|
|
@@ -804,4 +829,5 @@ module.exports = {
|
|
|
804
829
|
videoUnsupported,
|
|
805
830
|
videoWidthMax,
|
|
806
831
|
videoWidthMin,
|
|
832
|
+
workflowMissing,
|
|
807
833
|
};
|
package/src/schema/game.js
CHANGED
|
@@ -130,6 +130,19 @@ const atLeast = function (field, min, unit) {
|
|
|
130
130
|
|
|
131
131
|
// [function]
|
|
132
132
|
|
|
133
|
+
// a required string's error: "Must be defined." and `lines` when it's missing,
|
|
134
|
+
// and zod's own type message (null) when it's there but isn't a string.
|
|
135
|
+
const required = function (field, lines) {
|
|
136
|
+
const missing = message(field, ["Must be defined.", ...lines]).message;
|
|
137
|
+
return {
|
|
138
|
+
error(issue) {
|
|
139
|
+
return typeof issue.input === "undefined" ? missing : null;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// [function]
|
|
145
|
+
|
|
133
146
|
// a list of distinct values. Without a max, its length is up to the values.
|
|
134
147
|
const zSet = function (field, schema, { max = Infinity, min }) {
|
|
135
148
|
return z
|
|
@@ -422,6 +435,7 @@ const zHandle = function () {
|
|
|
422
435
|
return z.optional(
|
|
423
436
|
z
|
|
424
437
|
.string()
|
|
438
|
+
.min(1, { ...atLeast(field, 1), abort: true })
|
|
425
439
|
.regex(
|
|
426
440
|
/^[a-z0-9](?:(?![-]{2})[a-z0-9-]){1,22}[a-z0-9]$/u,
|
|
427
441
|
message(field, ["Contains invalid characters."]),
|
|
@@ -435,10 +449,7 @@ const zId = function () {
|
|
|
435
449
|
const field = "id";
|
|
436
450
|
return z
|
|
437
451
|
.string(
|
|
438
|
-
|
|
439
|
-
"Must be defined.",
|
|
440
|
-
`Run ${phrase.cmdInit()} to generate a new game ID.`,
|
|
441
|
-
]),
|
|
452
|
+
required(field, [`Run ${phrase.cmdInit()} to generate a new game ID.`]),
|
|
442
453
|
)
|
|
443
454
|
.uuid(message(field, ["Must be a valid UUID."]))
|
|
444
455
|
.refine(
|
|
@@ -499,6 +510,7 @@ const zTrailer = function () {
|
|
|
499
510
|
const field = "trailer";
|
|
500
511
|
return z
|
|
501
512
|
.string()
|
|
513
|
+
.min(1, { ...atLeast(field, 1), abort: true })
|
|
502
514
|
.max(255, message(field, ["Must not exceed: 255 characters"]))
|
|
503
515
|
.refine(
|
|
504
516
|
(url) =>
|
|
@@ -514,10 +526,7 @@ const zVersion = function () {
|
|
|
514
526
|
const field = "version";
|
|
515
527
|
return z
|
|
516
528
|
.string(
|
|
517
|
-
|
|
518
|
-
"Must be defined.",
|
|
519
|
-
"A version is required unless one can be derived.",
|
|
520
|
-
]),
|
|
529
|
+
required(field, ["A version is required unless one can be derived."]),
|
|
521
530
|
)
|
|
522
531
|
.refine(
|
|
523
532
|
(value) => validSemver(value) !== null,
|
package/src/util/output.js
CHANGED
|
@@ -62,13 +62,14 @@ const print = function (data, { key, nl, toml } = {}) {
|
|
|
62
62
|
// aborted.") and the reason on stderr, then its code and the link to it on
|
|
63
63
|
// the Errors page, once the spinner and progress bar are cleared. Then exits
|
|
64
64
|
// 1. `data`, and the error's own, go beside the error in JSON, such as the
|
|
65
|
-
// release that failed.
|
|
65
|
+
// release that failed. `nl` puts a blank line before the heading, after
|
|
66
|
+
// output the command has already printed, such as arc login's link.
|
|
66
67
|
// `code` is a failed release's reason, which the API may add to, so it isn't
|
|
67
68
|
// checked, and its message already says it.
|
|
68
69
|
const abort = function (
|
|
69
70
|
heading,
|
|
70
71
|
ex,
|
|
71
|
-
{ code, data, json = false, pb, sw } = {},
|
|
72
|
+
{ code, data, json = false, nl = false, pb, sw } = {},
|
|
72
73
|
) {
|
|
73
74
|
const message = ex.message;
|
|
74
75
|
const status = ex.status;
|
|
@@ -89,7 +90,11 @@ const abort = function (
|
|
|
89
90
|
if (sw) {
|
|
90
91
|
sw.stop(true);
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
if (nl) {
|
|
94
|
+
log.nlhd(heading);
|
|
95
|
+
} else {
|
|
96
|
+
log.hd(heading);
|
|
97
|
+
}
|
|
93
98
|
log.pd(message);
|
|
94
99
|
if (!code) {
|
|
95
100
|
log.nlpd([
|