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 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 error = require("../src/util/error");
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... %s");
62
+ let sw = new SpinnerWheel("Validating… %s");
66
63
  sw.start(json || quiet);
67
- return project(root)
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... %s");
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());
@@ -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", "Inspect project")
39
- .command("validate", "Validate project")
40
- .command("build", "Build project")
41
- .command("pack", "Package project")
42
- .command("serve", "Serve a game locally")
43
- .command("deploy", "Deploy project")
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", `Show ${TITLE} account`)
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(`⚠ ${message}`);
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, [store ? ["Store:", store] : null, ["Play:", url], next]);
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... %s");
267
+ let sw = new SpinnerWheel("Validating… %s");
259
268
  sw.start(json || quiet);
260
269
  return checkTimeout(timeout)
261
- .then(() => project(root))
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(validated.warnings, { json, quiet });
278
- sw = new SpinnerWheel("Checking... %s");
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... %s");
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
- sw = new SpinnerWheel("Building... %s");
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... %s");
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 = { ...deployed };
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
- describe: "Seconds to wait [default: 300, or 900 for git]",
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... %s");
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
- process.stdout.write("", () => process.exit(0));
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 (disable with --no-browser)",
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: "Display a qrcode (disable with --no-qrcode)",
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 error = require("../src/util/error");
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... %s");
61
+ let sw = new SpinnerWheel("Validating… %s");
66
62
  sw.start(json || quiet);
67
- return project(root)
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... %s");
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: "Set the input directory",
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: "Set the output file",
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... %s");
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... %s");
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
- ["Play:", `${served.page}/`],
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: "Port to serve on",
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
  })
@@ -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
- process.exit(count ? 1 : 0);
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... %s");
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
@@ -65,5 +65,5 @@
65
65
  "scripts": {
66
66
  "test": "node --test \"test/**/*.test.js\""
67
67
  },
68
- "version": "1.0.1"
68
+ "version": "1.0.4"
69
69
  }
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
- if (ex.code === "ENOENT") {
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 uploaded again within 5 minutes.
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.UPLOAD_RATE_LIMITED,
47
- phrase.uploadRateLimited(version),
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, whoever owns it. A game without a
42
- // handle has none to check.
43
- const ensureHandle = function ({ handle, id }) {
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
- return api.discoverGame(handle).then((game) => {
48
- if (game.id !== id) {
49
- throw new ArcError(
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
- }, ignoreMissing);
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, and its handle if it has one, are
60
- // free or already its own.
61
- const ensureId = function (current, game) {
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]
@@ -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
- // [function]
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
- const res = await fetch(url, { method: "GET" });
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 (ex) {
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;