arcadible 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/arc-build.js +7 -22
- package/bin/arc-default.js +8 -8
- package/bin/arc-deploy.js +31 -23
- package/bin/arc-init.js +4 -3
- package/bin/arc-login.js +2 -2
- package/bin/arc-pack.js +8 -22
- package/bin/arc-play.js +1 -1
- package/bin/arc-serve.js +4 -3
- package/bin/arc-validate.js +2 -1
- package/bin/arc-whoami.js +1 -1
- package/package.json +1 -1
- package/src/account.js +3 -1
- package/src/action/deploy/tool.js +5 -4
- package/src/action/predeploy/ac.js +47 -15
- package/src/action/predeploy/tool.js +3 -3
- package/src/action/serve/tool.js +19 -3
- package/src/action/validate/checked.js +37 -0
- package/src/api.js +39 -12
- package/src/config/code.js +3 -1
- package/src/parser/readme.js +1 -1
- package/src/phrase.js +86 -45
- package/src/schema/game.js +32 -20
- package/src/schema/readme.js +2 -1
- package/src/schema/workflow.js +8 -10
- package/src/serve/game.js +7 -1
- package/src/serve/page.js +11 -11
- package/src/serve/reload.js +5 -0
- package/src/util/log.js +9 -2
- package/src/util/md.js +1 -1
- package/src/util/output.js +17 -2
- package/src/util/schema.js +53 -1
package/bin/arc-build.js
CHANGED
|
@@ -17,19 +17,16 @@ const build = require("../src/cli/build");
|
|
|
17
17
|
const cfgcode = require("../src/config/code");
|
|
18
18
|
const cfgpackage = require("../src/config/package");
|
|
19
19
|
const cfgproduct = require("../src/config/product");
|
|
20
|
-
const
|
|
20
|
+
const checked = require("../src/action/validate/checked");
|
|
21
21
|
const log = require("../src/util/log");
|
|
22
22
|
const output = require("../src/util/output");
|
|
23
23
|
const phrase = require("../src/phrase");
|
|
24
|
-
const project = require("../src/parser/project");
|
|
25
24
|
const rc = require("../src/util/rc");
|
|
26
25
|
const spinner = require("../src/util/spinner");
|
|
27
|
-
const validate = require("../src/cli/validate");
|
|
28
26
|
|
|
29
27
|
// [use]
|
|
30
28
|
|
|
31
29
|
const abort = output.abort;
|
|
32
|
-
const ArcError = error.ArcError;
|
|
33
30
|
const findRc = rc.findRc;
|
|
34
31
|
const print = output.print;
|
|
35
32
|
const relative = path.relative;
|
|
@@ -62,26 +59,16 @@ const start = function (argv) {
|
|
|
62
59
|
let parsed = null;
|
|
63
60
|
// what --json lists under `warnings`.
|
|
64
61
|
let warnings = [];
|
|
65
|
-
let sw = new SpinnerWheel("Validating
|
|
62
|
+
let sw = new SpinnerWheel("Validating… %s");
|
|
66
63
|
sw.start(json || quiet);
|
|
67
|
-
return
|
|
64
|
+
return checked(root, (found) => {
|
|
65
|
+
warnings = found;
|
|
66
|
+
})
|
|
68
67
|
.then((proj) => {
|
|
69
68
|
parsed = proj;
|
|
70
|
-
return validate({ project: parsed, root });
|
|
71
|
-
})
|
|
72
|
-
.then((validated) => {
|
|
73
|
-
const count = validated.issues.length;
|
|
74
|
-
warnings = validated.warnings;
|
|
75
|
-
if (count) {
|
|
76
|
-
throw new ArcError(
|
|
77
|
-
cfgcode.VALIDATION_FAILED,
|
|
78
|
-
phrase.issuesFound(count),
|
|
79
|
-
{ data: { issues: validated.issues } },
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
69
|
sw.stop(true);
|
|
83
70
|
warn(warnings, { json, quiet });
|
|
84
|
-
sw = new SpinnerWheel("Building
|
|
71
|
+
sw = new SpinnerWheel("Building… %s");
|
|
85
72
|
sw.start(json || quiet);
|
|
86
73
|
return build({ project: parsed, root });
|
|
87
74
|
})
|
|
@@ -110,9 +97,7 @@ const start = function (argv) {
|
|
|
110
97
|
}
|
|
111
98
|
if (artifact?.missing) {
|
|
112
99
|
log.result(phrase.buildSucceeded(), [], { color: "yellow" });
|
|
113
|
-
log.nlpw(
|
|
114
|
-
`⚠ ${phrase.buildOutputMissing(relative(root, artifact.missing))}`,
|
|
115
|
-
);
|
|
100
|
+
log.nlpw(phrase.buildOutputMissing(relative(root, artifact.missing)));
|
|
116
101
|
return;
|
|
117
102
|
}
|
|
118
103
|
log.hs(phrase.nothingToDo());
|
package/bin/arc-default.js
CHANGED
|
@@ -34,17 +34,17 @@ const main = function (argv) {
|
|
|
34
34
|
yargs(args.normalize(hideBin(argv)))
|
|
35
35
|
.scriptName(TOOL)
|
|
36
36
|
.usage("$0 <command> [options]")
|
|
37
|
-
.command("init", "Initialize project")
|
|
38
|
-
.command("inspect", "
|
|
39
|
-
.command("validate", "
|
|
40
|
-
.command("build", "
|
|
41
|
-
.command("pack", "
|
|
42
|
-
.command("serve", "Serve
|
|
43
|
-
.command("deploy", "Deploy
|
|
37
|
+
.command("init", "Initialize a project")
|
|
38
|
+
.command("inspect", "Print the resolved manifest")
|
|
39
|
+
.command("validate", "Check the project for issues")
|
|
40
|
+
.command("build", "Run the project's build")
|
|
41
|
+
.command("pack", "Pack the game into a zip")
|
|
42
|
+
.command("serve", "Serve the game locally")
|
|
43
|
+
.command("deploy", "Deploy the game")
|
|
44
44
|
.command("open", "Open a game's store page")
|
|
45
45
|
.command("play", "Play a game")
|
|
46
46
|
.command("login", `Log in to ${TITLE}`)
|
|
47
|
-
.command("whoami",
|
|
47
|
+
.command("whoami", "Show the logged-in account")
|
|
48
48
|
.command("logout", `Log out of ${TITLE}`)
|
|
49
49
|
.help("help")
|
|
50
50
|
.alias("help", "h")
|
package/bin/arc-deploy.js
CHANGED
|
@@ -17,8 +17,10 @@ const build = require("../src/cli/build");
|
|
|
17
17
|
const cfgcode = require("../src/config/code");
|
|
18
18
|
const cfgpackage = require("../src/config/package");
|
|
19
19
|
const cfgproduct = require("../src/config/product");
|
|
20
|
+
const checked = require("../src/action/validate/checked");
|
|
20
21
|
const deploy = require("../src/cli/deploy");
|
|
21
22
|
const error = require("../src/util/error");
|
|
23
|
+
const links = require("../src/util/links");
|
|
22
24
|
const log = require("../src/util/log");
|
|
23
25
|
const output = require("../src/util/output");
|
|
24
26
|
const pack = require("../src/cli/pack");
|
|
@@ -96,7 +98,7 @@ const handler = function ({ json, pb, quiet, sw, warnings }) {
|
|
|
96
98
|
if (pb) {
|
|
97
99
|
pb.stop();
|
|
98
100
|
}
|
|
99
|
-
log.pw(
|
|
101
|
+
log.pw(message);
|
|
100
102
|
sw.start(false);
|
|
101
103
|
if (pb) {
|
|
102
104
|
pb.start();
|
|
@@ -125,7 +127,11 @@ const report = function (
|
|
|
125
127
|
heading = phrase.releasePublished(version.to, channel, built);
|
|
126
128
|
next = null;
|
|
127
129
|
}
|
|
128
|
-
log.result(heading, [
|
|
130
|
+
log.result(heading, [
|
|
131
|
+
store ? [phrase.storeLabel(), store] : null,
|
|
132
|
+
[phrase.playLabel(), url],
|
|
133
|
+
next,
|
|
134
|
+
]);
|
|
129
135
|
};
|
|
130
136
|
|
|
131
137
|
// [function]
|
|
@@ -250,32 +256,27 @@ const start = function (argv) {
|
|
|
250
256
|
// the game's account, when Arcadible could say: a git deploy's owner waits
|
|
251
257
|
// among their own releases.
|
|
252
258
|
let owner = null;
|
|
259
|
+
// whether the handle rules held the handle back from this release, so the
|
|
260
|
+
// game's links are by id.
|
|
261
|
+
let held = false;
|
|
253
262
|
// the project, parsed once and given to each step. A build can write files
|
|
254
263
|
// the manifest reads, such as art, so it's parsed again after one runs.
|
|
255
264
|
let parsed = null;
|
|
256
265
|
// what --json lists under `warnings`: arc validate's, then the deploy's.
|
|
257
266
|
const warnings = [];
|
|
258
|
-
let sw = new SpinnerWheel("Validating
|
|
267
|
+
let sw = new SpinnerWheel("Validating… %s");
|
|
259
268
|
sw.start(json || quiet);
|
|
260
269
|
return checkTimeout(timeout)
|
|
261
|
-
.then(() =>
|
|
270
|
+
.then(() =>
|
|
271
|
+
checked(root, (found) => {
|
|
272
|
+
warnings.push(...found);
|
|
273
|
+
}),
|
|
274
|
+
)
|
|
262
275
|
.then((proj) => {
|
|
263
276
|
parsed = proj;
|
|
264
|
-
return validate({ project: parsed, root });
|
|
265
|
-
})
|
|
266
|
-
.then((validated) => {
|
|
267
|
-
const count = validated.issues.length;
|
|
268
|
-
warnings.push(...validated.warnings);
|
|
269
|
-
if (count) {
|
|
270
|
-
throw new ArcError(
|
|
271
|
-
cfgcode.VALIDATION_FAILED,
|
|
272
|
-
phrase.issuesFound(count),
|
|
273
|
-
{ data: { issues: validated.issues } },
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
277
|
sw.stop(true);
|
|
277
|
-
warn(
|
|
278
|
-
sw = new SpinnerWheel("Checking
|
|
278
|
+
warn(warnings, { json, quiet });
|
|
279
|
+
sw = new SpinnerWheel("Checking… %s");
|
|
279
280
|
// git deploys need no login: anyone who can push to the repository can
|
|
280
281
|
// release, and the Arcadible GitHub App publishes the release. Anyone
|
|
281
282
|
// can wait for it too; the game's owner, logged in, waits among their
|
|
@@ -292,7 +293,7 @@ const start = function (argv) {
|
|
|
292
293
|
throw new ArcError(cfgcode.NOT_LOGGED_IN, phrase.notLoggedIn());
|
|
293
294
|
}
|
|
294
295
|
sw.stop(true);
|
|
295
|
-
sw = new SpinnerWheel("Preparing
|
|
296
|
+
sw = new SpinnerWheel("Preparing… %s");
|
|
296
297
|
return Promise.all([
|
|
297
298
|
// whoami
|
|
298
299
|
current,
|
|
@@ -309,7 +310,8 @@ const start = function (argv) {
|
|
|
309
310
|
.then(([current, predeployed]) => {
|
|
310
311
|
sw.stop(true);
|
|
311
312
|
owner = predeployed.owner ?? null;
|
|
312
|
-
|
|
313
|
+
held = predeployed.held ?? false;
|
|
314
|
+
sw = new SpinnerWheel("Building… %s");
|
|
313
315
|
sw.start(git || json || quiet);
|
|
314
316
|
return Promise.all([
|
|
315
317
|
// whoami
|
|
@@ -340,7 +342,7 @@ const start = function (argv) {
|
|
|
340
342
|
}
|
|
341
343
|
parsed = proj;
|
|
342
344
|
sw.stop(true);
|
|
343
|
-
sw = new SpinnerWheel("Packing
|
|
345
|
+
sw = new SpinnerWheel("Packing… %s");
|
|
344
346
|
sw.start(git || json || quiet);
|
|
345
347
|
return Promise.all([
|
|
346
348
|
// whoami
|
|
@@ -378,7 +380,12 @@ const start = function (argv) {
|
|
|
378
380
|
pb.stop();
|
|
379
381
|
}
|
|
380
382
|
// the game and release are for waiting, not printing.
|
|
381
|
-
const shown = {
|
|
383
|
+
const shown = {
|
|
384
|
+
...deployed,
|
|
385
|
+
...(held
|
|
386
|
+
? links.published({ id: deployed.game, version: deployed.version.to })
|
|
387
|
+
: {}),
|
|
388
|
+
};
|
|
382
389
|
delete shown.game;
|
|
383
390
|
delete shown.release;
|
|
384
391
|
const result = { ...(git ? shown : { built, ...shown }), warnings };
|
|
@@ -431,7 +438,8 @@ const main = function (argv) {
|
|
|
431
438
|
})
|
|
432
439
|
.option("timeout", {
|
|
433
440
|
default: conf.timeout,
|
|
434
|
-
|
|
441
|
+
defaultDescription: "300, or 900 for git",
|
|
442
|
+
describe: "Seconds to wait",
|
|
435
443
|
type: "number",
|
|
436
444
|
})
|
|
437
445
|
.option("cwd", args.CWD)
|
package/bin/arc-init.js
CHANGED
|
@@ -31,6 +31,7 @@ const spinner = require("../src/util/spinner");
|
|
|
31
31
|
|
|
32
32
|
const abort = output.abort;
|
|
33
33
|
const ArcError = error.ArcError;
|
|
34
|
+
const exit = output.exit;
|
|
34
35
|
const findRc = rc.findRc;
|
|
35
36
|
const pathExists = fs.pathExists;
|
|
36
37
|
const print = output.print;
|
|
@@ -69,7 +70,7 @@ const handler = function ({ json, quiet, sw }) {
|
|
|
69
70
|
const start = function (argv) {
|
|
70
71
|
const json = argv.json;
|
|
71
72
|
const quiet = argv.quiet;
|
|
72
|
-
const sw = new SpinnerWheel("Initializing
|
|
73
|
+
const sw = new SpinnerWheel("Initializing… %s");
|
|
73
74
|
return init({
|
|
74
75
|
handler: handler({ json, quiet, sw }),
|
|
75
76
|
patch: argv.patch,
|
|
@@ -86,7 +87,7 @@ const start = function (argv) {
|
|
|
86
87
|
print(project, { nl: true, toml: true });
|
|
87
88
|
}
|
|
88
89
|
// degit sometimes hangs, so exit once stdout is written.
|
|
89
|
-
|
|
90
|
+
return exit(0);
|
|
90
91
|
})
|
|
91
92
|
.catch((ex) => abort(phrase.aborted(COMMAND), ex, { json, sw }));
|
|
92
93
|
};
|
|
@@ -149,7 +150,7 @@ const main = function (argv) {
|
|
|
149
150
|
default: conf.template,
|
|
150
151
|
describe: [
|
|
151
152
|
"Project template",
|
|
152
|
-
"Public GitHub repository, or a folder in one (user/repo[/folder])
|
|
153
|
+
"Public GitHub repository, or a folder in one (user/repo[/folder])",
|
|
153
154
|
].join("\n"),
|
|
154
155
|
type: "string",
|
|
155
156
|
})
|
package/bin/arc-login.js
CHANGED
|
@@ -137,12 +137,12 @@ const main = function (argv) {
|
|
|
137
137
|
.usage("$0 [options]")
|
|
138
138
|
.option("browser", {
|
|
139
139
|
default: conf.browser,
|
|
140
|
-
describe: "Open a web browser on Enter
|
|
140
|
+
describe: "Open a web browser on Enter",
|
|
141
141
|
type: "boolean",
|
|
142
142
|
})
|
|
143
143
|
.option("qrcode", {
|
|
144
144
|
default: conf.qrcode,
|
|
145
|
-
describe: "
|
|
145
|
+
describe: "Show a QR code",
|
|
146
146
|
type: "boolean",
|
|
147
147
|
})
|
|
148
148
|
.help("help")
|
package/bin/arc-pack.js
CHANGED
|
@@ -13,23 +13,19 @@ const yargs = require("yargs/yargs");
|
|
|
13
13
|
// [import]
|
|
14
14
|
|
|
15
15
|
const args = require("../src/util/argv");
|
|
16
|
-
const cfgcode = require("../src/config/code");
|
|
17
16
|
const cfgpackage = require("../src/config/package");
|
|
18
17
|
const cfgproduct = require("../src/config/product");
|
|
19
|
-
const
|
|
18
|
+
const checked = require("../src/action/validate/checked");
|
|
20
19
|
const log = require("../src/util/log");
|
|
21
20
|
const output = require("../src/util/output");
|
|
22
21
|
const pack = require("../src/cli/pack");
|
|
23
22
|
const phrase = require("../src/phrase");
|
|
24
|
-
const project = require("../src/parser/project");
|
|
25
23
|
const rc = require("../src/util/rc");
|
|
26
24
|
const spinner = require("../src/util/spinner");
|
|
27
|
-
const validate = require("../src/cli/validate");
|
|
28
25
|
|
|
29
26
|
// [use]
|
|
30
27
|
|
|
31
28
|
const abort = output.abort;
|
|
32
|
-
const ArcError = error.ArcError;
|
|
33
29
|
const findRc = rc.findRc;
|
|
34
30
|
const print = output.print;
|
|
35
31
|
const resolve = path.resolve;
|
|
@@ -62,26 +58,16 @@ const start = function (argv) {
|
|
|
62
58
|
let parsed = null;
|
|
63
59
|
// what --json lists under `warnings`.
|
|
64
60
|
let warnings = [];
|
|
65
|
-
let sw = new SpinnerWheel("Validating
|
|
61
|
+
let sw = new SpinnerWheel("Validating… %s");
|
|
66
62
|
sw.start(json || quiet);
|
|
67
|
-
return
|
|
63
|
+
return checked(root, (found) => {
|
|
64
|
+
warnings = found;
|
|
65
|
+
})
|
|
68
66
|
.then((proj) => {
|
|
69
67
|
parsed = proj;
|
|
70
|
-
return validate({ project: parsed, root });
|
|
71
|
-
})
|
|
72
|
-
.then((validated) => {
|
|
73
|
-
const count = validated.issues.length;
|
|
74
|
-
warnings = validated.warnings;
|
|
75
|
-
if (count) {
|
|
76
|
-
throw new ArcError(
|
|
77
|
-
cfgcode.VALIDATION_FAILED,
|
|
78
|
-
phrase.issuesFound(count),
|
|
79
|
-
{ data: { issues: validated.issues } },
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
68
|
sw.stop(true);
|
|
83
69
|
warn(warnings, { json, quiet });
|
|
84
|
-
sw = new SpinnerWheel("Packing
|
|
70
|
+
sw = new SpinnerWheel("Packing… %s");
|
|
85
71
|
sw.start(json || quiet);
|
|
86
72
|
return pack({
|
|
87
73
|
input: argv.input,
|
|
@@ -116,13 +102,13 @@ const main = function (argv) {
|
|
|
116
102
|
.option("input", {
|
|
117
103
|
alias: "i",
|
|
118
104
|
default: conf.input,
|
|
119
|
-
describe: "
|
|
105
|
+
describe: "Pack this directory instead of deploy_dir",
|
|
120
106
|
type: "string",
|
|
121
107
|
})
|
|
122
108
|
.option("output", {
|
|
123
109
|
alias: "o",
|
|
124
110
|
default: conf.output,
|
|
125
|
-
describe: "
|
|
111
|
+
describe: "Write the zip to this path",
|
|
126
112
|
type: "string",
|
|
127
113
|
})
|
|
128
114
|
.option("json", { ...args.JSON_OUTPUT, default: conf.json })
|
package/bin/arc-play.js
CHANGED
|
@@ -64,7 +64,7 @@ const handler = function ({ quiet, sw }) {
|
|
|
64
64
|
// [function]
|
|
65
65
|
|
|
66
66
|
const start = function (argv) {
|
|
67
|
-
const sw = new SpinnerWheel("Opening
|
|
67
|
+
const sw = new SpinnerWheel("Opening… %s");
|
|
68
68
|
return play({
|
|
69
69
|
game: argv.game,
|
|
70
70
|
handler: handler({ quiet: argv.quiet, sw }),
|
package/bin/arc-serve.js
CHANGED
|
@@ -111,7 +111,7 @@ const start = function (argv) {
|
|
|
111
111
|
// `.arcrc` values are strings, and a flag that isn't a number is NaN.
|
|
112
112
|
const port = Number(argv.port);
|
|
113
113
|
const root = resolve(process.cwd(), argv.cwd);
|
|
114
|
-
const sw = new SpinnerWheel("Building
|
|
114
|
+
const sw = new SpinnerWheel("Building… %s");
|
|
115
115
|
return checkPort(port)
|
|
116
116
|
.then(() => checkUrl(argv.url))
|
|
117
117
|
.then((url) =>
|
|
@@ -141,7 +141,7 @@ const start = function (argv) {
|
|
|
141
141
|
process.once("SIGTERM", stop);
|
|
142
142
|
if (!quiet) {
|
|
143
143
|
log.result(phrase.served(served), [
|
|
144
|
-
[
|
|
144
|
+
[phrase.playLabel(), `${served.page}/`],
|
|
145
145
|
[phrase.serveAlone(), served.game],
|
|
146
146
|
served.url
|
|
147
147
|
? phrase.serveNextUrl()
|
|
@@ -173,7 +173,7 @@ const main = function (argv) {
|
|
|
173
173
|
.option("port", {
|
|
174
174
|
alias: "p",
|
|
175
175
|
default: conf.port,
|
|
176
|
-
describe: "
|
|
176
|
+
describe: "Serve on this port",
|
|
177
177
|
type: "number",
|
|
178
178
|
})
|
|
179
179
|
.option("build", {
|
|
@@ -182,6 +182,7 @@ const main = function (argv) {
|
|
|
182
182
|
type: "boolean",
|
|
183
183
|
})
|
|
184
184
|
.option("url", {
|
|
185
|
+
default: conf.url,
|
|
185
186
|
describe: "Frame another server's page instead of deploy_dir",
|
|
186
187
|
type: "string",
|
|
187
188
|
})
|
package/bin/arc-validate.js
CHANGED
|
@@ -26,6 +26,7 @@ const validate = require("../src/cli/validate");
|
|
|
26
26
|
|
|
27
27
|
const abort = output.abort;
|
|
28
28
|
const dereference = schema.dereference;
|
|
29
|
+
const exit = output.exit;
|
|
29
30
|
const findRc = rc.findRc;
|
|
30
31
|
const print = output.print;
|
|
31
32
|
const resolve = path.resolve;
|
|
@@ -75,7 +76,7 @@ const start = function (argv) {
|
|
|
75
76
|
log.hs(phrase.noIssuesFound());
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
+
return exit(count ? 1 : 0);
|
|
79
80
|
})
|
|
80
81
|
.catch((ex) => abort(phrase.aborted(COMMAND), ex, { json }));
|
|
81
82
|
};
|
package/bin/arc-whoami.js
CHANGED
|
@@ -54,7 +54,7 @@ const handler = function ({ json, sw }) {
|
|
|
54
54
|
|
|
55
55
|
const start = function (argv) {
|
|
56
56
|
const json = argv.json;
|
|
57
|
-
const sw = new SpinnerWheel("Checking
|
|
57
|
+
const sw = new SpinnerWheel("Checking… %s");
|
|
58
58
|
return whoami({ handler: handler({ json, sw }) })
|
|
59
59
|
.then((current) => {
|
|
60
60
|
const user = current?.user;
|
package/package.json
CHANGED
package/src/account.js
CHANGED
|
@@ -102,7 +102,9 @@ const getSaved = function (onRefresh) {
|
|
|
102
102
|
return readFile(file, "utf8")
|
|
103
103
|
.then(JSON.parse)
|
|
104
104
|
.catch((ex) => {
|
|
105
|
-
|
|
105
|
+
// a session file cut short (a full disk, a crash while saving) is no
|
|
106
|
+
// session: `arc login` writes a new one
|
|
107
|
+
if (ex.code === "ENOENT" || ex instanceof SyntaxError) {
|
|
106
108
|
return null;
|
|
107
109
|
}
|
|
108
110
|
throw ex;
|
|
@@ -33,18 +33,19 @@ const uploadBuild = function (current, artifact, { id, version }, onProgress) {
|
|
|
33
33
|
s3.upload(upload.upload_url, artifact, onProgress),
|
|
34
34
|
]),
|
|
35
35
|
(err) => {
|
|
36
|
-
// the API refuses a game that was deleted or is another account's
|
|
37
|
-
// and the same version
|
|
36
|
+
// the API refuses a game that was deleted or is another account's
|
|
37
|
+
// (403), and the same version deployed again within 5 minutes (429).
|
|
38
38
|
if (err.status === 403) {
|
|
39
39
|
throw new ArcError(
|
|
40
40
|
cfgcode.GAME_UNAVAILABLE,
|
|
41
41
|
phrase.gameUnavailable(id),
|
|
42
|
+
{ cause: err },
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
45
|
if (err.status === 429) {
|
|
45
46
|
throw new ArcError(
|
|
46
|
-
cfgcode.
|
|
47
|
-
phrase.
|
|
47
|
+
cfgcode.DEPLOY_RATE_LIMITED,
|
|
48
|
+
phrase.deployRateLimited(version),
|
|
48
49
|
{ cause: err },
|
|
49
50
|
);
|
|
50
51
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const api = require("../../api");
|
|
6
6
|
const cfgcode = require("../../config/code");
|
|
7
7
|
const error = require("../../util/error");
|
|
8
|
+
const links = require("../../util/links");
|
|
8
9
|
const phrase = require("../../phrase");
|
|
9
10
|
const published = require("./published");
|
|
10
11
|
|
|
@@ -38,30 +39,61 @@ const ensureOwned = function (current, id) {
|
|
|
38
39
|
|
|
39
40
|
// [function]
|
|
40
41
|
|
|
41
|
-
// fails when the handle is another game's,
|
|
42
|
-
// handle
|
|
43
|
-
|
|
42
|
+
// fails when the handle is taken: another game's, held for another account,
|
|
43
|
+
// or reserved. When the handle rules keep it from this release (the limit on
|
|
44
|
+
// unreleased games' handles, or one that lapsed from this account), it warns
|
|
45
|
+
// and deploys anyway, as the game keeps the handle it has, or none, and
|
|
46
|
+
// resolves with true. A game without a handle has none to check.
|
|
47
|
+
const ensureHandle = function (current, { handle, id, version }, handler) {
|
|
44
48
|
if ((handle ?? null) === null) {
|
|
45
49
|
return Promise.resolve(null);
|
|
46
50
|
}
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
cfgcode.GAME_HANDLE_TAKEN,
|
|
51
|
-
phrase.gameHandleTaken(handle),
|
|
52
|
-
);
|
|
51
|
+
const warn = (code, message) => {
|
|
52
|
+
if (handler?.onWarning) {
|
|
53
|
+
handler.onWarning({ code, message });
|
|
53
54
|
}
|
|
54
|
-
}
|
|
55
|
+
};
|
|
56
|
+
return api
|
|
57
|
+
.gameHandle(
|
|
58
|
+
id,
|
|
59
|
+
handle,
|
|
60
|
+
{ channel: links.channel(version) },
|
|
61
|
+
api.auth(current?.session?.access_token),
|
|
62
|
+
)
|
|
63
|
+
.then(({ status }) => {
|
|
64
|
+
if (status === "taken") {
|
|
65
|
+
throw new ArcError(
|
|
66
|
+
cfgcode.GAME_HANDLE_TAKEN,
|
|
67
|
+
phrase.gameHandleTaken(handle),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (status === "limit") {
|
|
71
|
+
warn(
|
|
72
|
+
cfgcode.WARNING.GAME_HANDLE_LIMIT_REACHED,
|
|
73
|
+
phrase.gameHandleLimit(handle),
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (status === "lapsed") {
|
|
78
|
+
warn(
|
|
79
|
+
cfgcode.WARNING.GAME_HANDLE_LAPSED,
|
|
80
|
+
phrase.gameHandleLapsed(handle),
|
|
81
|
+
);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}, ignoreMissing);
|
|
55
86
|
};
|
|
56
87
|
|
|
57
88
|
// [function]
|
|
58
89
|
|
|
59
|
-
// resolves with the game once its id
|
|
60
|
-
//
|
|
61
|
-
|
|
90
|
+
// resolves with the game once its id is free or already its own, and its
|
|
91
|
+
// handle, if it has one, isn't taken; `held` when the rules hold the handle
|
|
92
|
+
// back from this release, so its links are by id.
|
|
93
|
+
const ensureId = function (current, game, handler) {
|
|
62
94
|
return ensureOwned(current, game.id)
|
|
63
|
-
.then(() => ensureHandle(game))
|
|
64
|
-
.then(() => game);
|
|
95
|
+
.then(() => ensureHandle(current, game, handler))
|
|
96
|
+
.then((held) => ({ ...game, held: Boolean(held) }));
|
|
65
97
|
};
|
|
66
98
|
|
|
67
99
|
// [function]
|
|
@@ -12,14 +12,14 @@ const ensureUnpublished = ac.ensureUnpublished;
|
|
|
12
12
|
|
|
13
13
|
// [function]
|
|
14
14
|
|
|
15
|
-
const main = function (current, conf, { onPredeployBegin }) {
|
|
15
|
+
const main = function (current, conf, { onPredeployBegin, ...handler }) {
|
|
16
16
|
if (onPredeployBegin) {
|
|
17
17
|
onPredeployBegin({ root: conf.root });
|
|
18
18
|
}
|
|
19
19
|
return given(conf)
|
|
20
|
-
.then(({ game }) => ensureId(current, game))
|
|
20
|
+
.then(({ game }) => ensureId(current, game, handler))
|
|
21
21
|
.then((game) => ensureUnpublished(current, game))
|
|
22
|
-
.then((game) => ({ to: game.version }));
|
|
22
|
+
.then((game) => ({ held: game.held, to: game.version }));
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
// [export]
|
package/src/action/serve/tool.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// [import]
|
|
7
7
|
|
|
8
8
|
const path = require("node:path");
|
|
9
|
+
const timers = require("node:timers/promises");
|
|
9
10
|
|
|
10
11
|
// [import]
|
|
11
12
|
|
|
@@ -39,6 +40,7 @@ const relative = path.relative;
|
|
|
39
40
|
const remoteFiles = remote.remoteFiles;
|
|
40
41
|
const resolvePath = path.resolve;
|
|
41
42
|
const serve = server.serve;
|
|
43
|
+
const sleep = timers.setTimeout;
|
|
42
44
|
const watchDir = reload.watchDir;
|
|
43
45
|
|
|
44
46
|
// [declare]
|
|
@@ -48,7 +50,7 @@ const CHANNEL = "local";
|
|
|
48
50
|
// the saves of a game with no id yet, kept apart from any other's.
|
|
49
51
|
const UNNAMED = "local";
|
|
50
52
|
|
|
51
|
-
// [
|
|
53
|
+
// [declare]
|
|
52
54
|
|
|
53
55
|
// the headers a page needs for the play page to frame it: the play page is
|
|
54
56
|
// cross-origin isolated, so a cross-origin frame must be too, and allow it.
|
|
@@ -57,19 +59,33 @@ const FRAMEABLE = {
|
|
|
57
59
|
"cross-origin-resource-policy": ["cross-origin"],
|
|
58
60
|
};
|
|
59
61
|
|
|
62
|
+
// [declare]
|
|
63
|
+
|
|
64
|
+
// how long `url` has to answer before it counts as unreachable: a server
|
|
65
|
+
// that takes the connection and never answers would otherwise hold `arc
|
|
66
|
+
// serve` for minutes.
|
|
67
|
+
const PROBE_MS = 10000;
|
|
68
|
+
|
|
60
69
|
// [function]
|
|
61
70
|
|
|
62
71
|
// what `url` says when asked: `{ reachable, frameable }`, where `frameable`
|
|
63
72
|
// says whether it sends the headers the play page needs to frame it.
|
|
64
73
|
const probe = async function (url) {
|
|
65
74
|
try {
|
|
66
|
-
|
|
75
|
+
// null when it hasn't answered in time
|
|
76
|
+
const res = await Promise.race([
|
|
77
|
+
fetch(url, { method: "GET" }),
|
|
78
|
+
sleep(PROBE_MS, null, { ref: false }),
|
|
79
|
+
]);
|
|
80
|
+
if (!res) {
|
|
81
|
+
return { frameable: false, reachable: false };
|
|
82
|
+
}
|
|
67
83
|
await res.body?.cancel();
|
|
68
84
|
const frameable = Object.entries(FRAMEABLE).every(([name, values]) =>
|
|
69
85
|
values.includes(res.headers.get(name)?.trim().toLowerCase()),
|
|
70
86
|
);
|
|
71
87
|
return { frameable, reachable: true };
|
|
72
|
-
} catch
|
|
88
|
+
} catch {
|
|
73
89
|
return { frameable: false, reachable: false };
|
|
74
90
|
}
|
|
75
91
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// [import]
|
|
4
|
+
|
|
5
|
+
const cfgcode = require("../../config/code");
|
|
6
|
+
const error = require("../../util/error");
|
|
7
|
+
const phrase = require("../../phrase");
|
|
8
|
+
const project = require("../../parser/project");
|
|
9
|
+
const validateTool = require("./tool");
|
|
10
|
+
|
|
11
|
+
// [use]
|
|
12
|
+
|
|
13
|
+
const ArcError = error.ArcError;
|
|
14
|
+
|
|
15
|
+
// [function]
|
|
16
|
+
|
|
17
|
+
// parses and validates the project at `root` for a command that goes on from
|
|
18
|
+
// it (arc build, arc pack, arc deploy): resolves with the parsed project, or
|
|
19
|
+
// rejects as validation_failed with its issues in `data`. `onWarnings` gets
|
|
20
|
+
// the warnings either way, for --json to list beside the result or the error.
|
|
21
|
+
const checked = async function (root, onWarnings) {
|
|
22
|
+
const parsed = await project(root);
|
|
23
|
+
const { issues, warnings } = await validateTool({ project: parsed, root });
|
|
24
|
+
onWarnings(warnings);
|
|
25
|
+
if (issues.length) {
|
|
26
|
+
throw new ArcError(
|
|
27
|
+
cfgcode.VALIDATION_FAILED,
|
|
28
|
+
phrase.issuesFound(issues.length),
|
|
29
|
+
{ data: { issues } },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// [export]
|
|
36
|
+
|
|
37
|
+
module.exports = checked;
|