arcadible 0.1.29 → 0.1.31

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.
Files changed (76) hide show
  1. package/README.md +66 -2
  2. package/bin/arc-build.js +18 -25
  3. package/bin/arc-default.js +3 -2
  4. package/bin/arc-deploy.js +198 -58
  5. package/bin/arc-init.js +32 -51
  6. package/bin/arc-inspect.js +7 -37
  7. package/bin/arc-login.js +6 -7
  8. package/bin/arc-logout.js +7 -8
  9. package/bin/arc-open.js +21 -21
  10. package/bin/arc-pack.js +11 -27
  11. package/bin/arc-play.js +28 -20
  12. package/bin/arc-validate.js +8 -20
  13. package/bin/arc-whoami.js +16 -10
  14. package/bin/arc.js +2 -2
  15. package/package.json +20 -6
  16. package/src/account.js +60 -7
  17. package/src/action/build/tool.js +12 -3
  18. package/src/action/deploy/tool-git.js +61 -11
  19. package/src/action/deploy/tool.js +47 -29
  20. package/src/action/deploy/wait.js +145 -0
  21. package/src/action/init/tool-patch.js +5 -28
  22. package/src/action/init/tool.js +8 -21
  23. package/src/action/inspect/tool.js +3 -38
  24. package/src/action/login/flow.js +1 -1
  25. package/src/action/open/tool.js +15 -3
  26. package/src/action/pack/limits.js +43 -0
  27. package/src/action/pack/tool.js +3 -7
  28. package/src/action/play/tool.js +74 -13
  29. package/src/action/predeploy/ac.js +14 -43
  30. package/src/action/predeploy/git.js +40 -61
  31. package/src/action/predeploy/published.js +69 -0
  32. package/src/action/predeploy/tool-git.js +58 -15
  33. package/src/action/predeploy/tool.js +5 -11
  34. package/src/action/validate/tool.js +28 -7
  35. package/src/api.js +56 -19
  36. package/src/cli/inspect.js +2 -2
  37. package/src/config/html.js +1 -1
  38. package/src/config/uri.js +1 -0
  39. package/src/parser/env.js +8 -3
  40. package/src/parser/manifest.js +16 -3
  41. package/src/parser/media.js +16 -11
  42. package/src/parser/project.js +38 -16
  43. package/src/parser/readme-file.js +69 -0
  44. package/src/parser/readme.js +37 -109
  45. package/src/parser/sections.js +107 -0
  46. package/src/phrase.js +374 -52
  47. package/src/schema/check/workflow.js +42 -5
  48. package/src/schema/game.js +105 -50
  49. package/src/schema/message.js +46 -0
  50. package/src/schema/readme.js +38 -0
  51. package/src/schema/workflow.js +27 -26
  52. package/src/util/argv.js +49 -0
  53. package/src/util/chars.js +30 -0
  54. package/src/util/delay.js +1 -1
  55. package/src/util/doc.js +50 -34
  56. package/src/util/http.js +5 -3
  57. package/src/util/image.js +46 -13
  58. package/src/util/links.js +63 -0
  59. package/src/util/log.js +36 -51
  60. package/src/util/md.js +39 -2
  61. package/src/util/output.js +34 -9
  62. package/src/util/paths.js +3 -1
  63. package/src/util/progress.js +19 -5
  64. package/src/util/rc.js +67 -11
  65. package/src/util/request.js +36 -32
  66. package/src/util/schema.js +12 -4
  67. package/src/util/shell.js +39 -7
  68. package/src/util/spinner.js +14 -3
  69. package/src/util/term.js +28 -0
  70. package/src/util/uris.js +8 -1
  71. package/src/util/video.js +49 -17
  72. package/src/util/zip.js +15 -0
  73. package/.eslintrc +0 -6
  74. package/src/action/inspect/media.js +0 -34
  75. package/src/config/game.js +0 -5
  76. package/src/util/dom.js +0 -46
package/README.md CHANGED
@@ -1,3 +1,67 @@
1
- # Arcadible CLI
1
+ <p align="center">
2
+ <a href="https://arcadible.com">
3
+ <img src="https://adn-arcadible.imgix.net/images/i18n/core/www/appicon-512.png?w=256" alt="Arcadible" height="96">
4
+ </a>
5
+ </p>
2
6
 
3
- The Arcadible command-line interface.
7
+ <h1 align="center">Arcadible CLI</h1>
8
+
9
+ <p align="center">
10
+ Create, play, and share games with the world.
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://developer.arcadible.com/references/arcadible-cli">Docs</a> ·
15
+ <a href="https://developer.arcadible.com/guides/games/getting-started">Getting Started</a> ·
16
+ <a href="https://arcadible.com">Arcadible</a> ·
17
+ <a href="https://studio.arcadible.com">Studio</a>
18
+ </p>
19
+
20
+ <br>
21
+
22
+ `arc` creates, validates, packages, and publishes games on [Arcadible](https://arcadible.com). Bring a game built with any web technology: if it outputs an `index.html`, it ships.
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install -g arcadible # requires Node.js 24+
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```sh
33
+ arc login
34
+ arc init star-drift && cd star-drift
35
+ arc deploy
36
+ ```
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.
39
+
40
+ ## Commands
41
+
42
+ | Command | What it does |
43
+ | -------------- | ------------------------------------------------------------------------------ |
44
+ | `arc init` | Creates a project from a template, or prepares an existing game |
45
+ | `arc validate` | Checks your project locally, with the same rules Arcadible uses |
46
+ | `arc inspect` | Prints the manifest Arcadible will see, or one value from it |
47
+ | `arc build` | Runs your project's build command |
48
+ | `arc pack` | Packages your project into the zip that `arc deploy` uploads |
49
+ | `arc deploy` | Validates, builds, packages, and publishes, directly or through GitHub Actions |
50
+ | `arc play` | Plays a deployed game from any channel or version |
51
+ | `arc open` | Opens a game's store page |
52
+ | `arc login` | Logs in to Arcadible with a device code |
53
+ | `arc logout` | Logs out |
54
+ | `arc whoami` | Shows the account you're logged in to |
55
+
56
+ Run `arc <command> --help` for its options, or see the [CLI reference](https://developer.arcadible.com/references/arcadible-cli).
57
+
58
+ ## Learn more
59
+
60
+ - [Getting Started](https://developer.arcadible.com/guides/games/getting-started): from an empty directory to a public game.
61
+ - [The manifest](https://developer.arcadible.com/references/arcadible-manifest): every field in `arcadible.toml`.
62
+ - [Deploying from GitHub](https://developer.arcadible.com/guides/publishing/deploying-from-github): release with a tag, built by GitHub Actions.
63
+ - [Releases](https://developer.arcadible.com/guides/publishing/releases): where a release's result shows, and how to fix a failed one.
64
+
65
+ ## License
66
+
67
+ Apache-2.0
package/bin/arc-build.js CHANGED
@@ -12,6 +12,7 @@ const yargs = require("yargs/yargs");
12
12
 
13
13
  // [import]
14
14
 
15
+ const args = require("../src/util/argv");
15
16
  const build = require("../src/cli/build");
16
17
  const cfgpackage = require("../src/config/package");
17
18
  const cfgproduct = require("../src/config/product");
@@ -24,8 +25,10 @@ const validate = require("../src/cli/validate");
24
25
 
25
26
  // [use]
26
27
 
28
+ const abort = output.abort;
27
29
  const findRc = rc.findRc;
28
30
  const print = output.print;
31
+ const relative = path.relative;
29
32
  const resolve = path.resolve;
30
33
  const SpinnerWheel = spinner.SpinnerWheel;
31
34
 
@@ -40,7 +43,7 @@ const VERSION = cfgpackage.version;
40
43
  const conf = {
41
44
  json: false,
42
45
  quiet: false,
43
- ...findRc(TOOL, COMMAND, process.argv, ["--cwd", "-C"]),
46
+ ...findRc(TOOL, COMMAND, process.argv),
44
47
  };
45
48
 
46
49
  // [function]
@@ -50,13 +53,14 @@ const start = function (argv) {
50
53
  const quiet = argv.quiet;
51
54
  const root = resolve(process.cwd(), argv.cwd);
52
55
  let sw = new SpinnerWheel("Validating... %s");
56
+ sw.start(json || quiet);
53
57
  return validate({ root })
54
58
  .then((issues) => {
55
59
  const count = issues.length;
56
60
  if (count) {
57
61
  throw new Error(phrase.issuesFound(count));
58
62
  }
59
- sw.stop();
63
+ sw.stop(true);
60
64
  sw = new SpinnerWheel("Building... %s");
61
65
  sw.start(json || quiet);
62
66
  return build({ root });
@@ -68,32 +72,26 @@ const start = function (argv) {
68
72
  }
69
73
  if (!quiet) {
70
74
  sw.stop(true);
71
- log.hs("Done.");
72
- if (artifact) {
73
- print(artifact, { toml: true });
75
+ log.hs("Built.");
76
+ if (artifact?.path) {
77
+ print(artifact, { nl: true, toml: true });
74
78
  return;
75
79
  }
76
- log.p("Nothing to do.");
80
+ if (artifact?.missing) {
81
+ log.nlpw(phrase.buildOutputMissing(relative(root, artifact.missing)));
82
+ return;
83
+ }
84
+ log.nlp("Nothing to do.");
77
85
  }
78
86
  })
79
- .catch((ex) => {
80
- const message = ex.message;
81
- if (json) {
82
- print({ error: { message } });
83
- } else {
84
- sw.stop(true);
85
- log.hd("Build aborted.");
86
- log.p(message);
87
- }
88
- process.exit(1);
89
- });
87
+ .catch((ex) => abort("Build aborted.", ex, { json, sw }));
90
88
  };
91
89
 
92
90
  // [function]
93
91
 
94
92
  const main = function (argv) {
95
93
  return start(
96
- yargs(hideBin(argv))
94
+ yargs(args.normalize(hideBin(argv)))
97
95
  .scriptName(`${TOOL} ${COMMAND}`)
98
96
  .usage("$0 [options]")
99
97
  .option("json", {
@@ -108,12 +106,7 @@ const main = function (argv) {
108
106
  describe: "Output only errors",
109
107
  type: "boolean",
110
108
  })
111
- .option("cwd", {
112
- alias: "C",
113
- default: ".",
114
- describe: "Set the working directory",
115
- type: "string",
116
- })
109
+ .option("cwd", args.CWD)
117
110
  .help("help")
118
111
  .alias("help", "h")
119
112
  .version(VERSION)
@@ -123,7 +116,7 @@ const main = function (argv) {
123
116
  );
124
117
  };
125
118
 
126
- // [main]
119
+ // [start]
127
120
 
128
121
  if (require.main === module) {
129
122
  main(process.argv);
@@ -8,6 +8,7 @@ const yargs = require("yargs/yargs");
8
8
 
9
9
  // [import]
10
10
 
11
+ const args = require("../src/util/argv");
11
12
  const cfgpackage = require("../src/config/package");
12
13
  const cfgproduct = require("../src/config/product");
13
14
 
@@ -30,7 +31,7 @@ const parse = function (cli) {
30
31
 
31
32
  const main = function (argv) {
32
33
  return parse(
33
- yargs(hideBin(argv))
34
+ yargs(args.normalize(hideBin(argv)))
34
35
  .scriptName(TOOL)
35
36
  .usage("$0 <command> [options]")
36
37
  .command("init", "Initialize project")
@@ -52,7 +53,7 @@ const main = function (argv) {
52
53
  );
53
54
  };
54
55
 
55
- // [main]
56
+ // [start]
56
57
 
57
58
  if (require.main === module) {
58
59
  main(process.argv);
package/bin/arc-deploy.js CHANGED
@@ -12,6 +12,7 @@ const yargs = require("yargs/yargs");
12
12
 
13
13
  // [import]
14
14
 
15
+ const args = require("../src/util/argv");
15
16
  const build = require("../src/cli/build");
16
17
  const cfgpackage = require("../src/config/package");
17
18
  const cfgproduct = require("../src/config/product");
@@ -25,10 +26,12 @@ const progress = require("../src/util/progress");
25
26
  const rc = require("../src/util/rc");
26
27
  const spinner = require("../src/util/spinner");
27
28
  const validate = require("../src/cli/validate");
29
+ const wait = require("../src/action/deploy/wait");
28
30
  const whoami = require("../src/cli/whoami");
29
31
 
30
32
  // [use]
31
33
 
34
+ const abort = output.abort;
32
35
  const findRc = rc.findRc;
33
36
  const print = output.print;
34
37
  const ProgressBar = progress.ProgressBar;
@@ -39,7 +42,11 @@ const SpinnerWheel = spinner.SpinnerWheel;
39
42
 
40
43
  const COMMAND = "deploy";
41
44
  const TOOL = cfgproduct.tool;
45
+ const SUBMITTING = `Submitting ${progress.FORMAT}`;
42
46
  const VERSION = cfgpackage.version;
47
+ // how long `--wait` waits, in seconds. A git release waits on its GitHub
48
+ // workflow first.
49
+ const TIMEOUT = { direct: 300, git: 900 };
43
50
 
44
51
  // [define]
45
52
 
@@ -47,7 +54,8 @@ const conf = {
47
54
  json: false,
48
55
  method: "direct",
49
56
  quiet: false,
50
- ...findRc(TOOL, COMMAND, process.argv, ["--cwd", "-C"]),
57
+ wait: true,
58
+ ...findRc(TOOL, COMMAND, process.argv),
51
59
  };
52
60
 
53
61
  // [function]
@@ -59,11 +67,8 @@ const handler = function ({ json, pb, quiet, sw }) {
59
67
  return refresh(true);
60
68
  },
61
69
  onDeployBegin() {
62
- if (!json && !quiet) {
63
- log.p("Submitting...");
64
- if (pb) {
65
- pb.start();
66
- }
70
+ if (!json && !quiet && pb) {
71
+ pb.start();
67
72
  }
68
73
  },
69
74
  onLogInfo({ level, message }) {
@@ -98,28 +103,153 @@ const handler = function ({ json, pb, quiet, sw }) {
98
103
 
99
104
  // [function]
100
105
 
106
+ // what was deployed, whether workflow.build ran, where it will be once it's
107
+ // published (the store only shows production), and what happens next. Once
108
+ // waited on, it's published, so it says so. `note` is a last block.
109
+ const report = function (
110
+ { built, channel, store, tag, url, version },
111
+ { git, note, published },
112
+ ) {
113
+ let heading = git
114
+ ? phrase.tagPushed(tag)
115
+ : phrase.buildSubmitted(version.to, channel, built);
116
+ let next = git
117
+ ? phrase.buildPublishingGit(channel)
118
+ : phrase.buildPublishing();
119
+ if (published) {
120
+ heading = phrase.releasePublished(version.to, channel, built);
121
+ next = null;
122
+ }
123
+ log.result(heading, [
124
+ store ? ["Store:", store] : null,
125
+ ["Play:", url],
126
+ next,
127
+ note ?? null,
128
+ ]);
129
+ };
130
+
131
+ // [function]
132
+
133
+ // the account, or null when there's none or it can't be refreshed: for a git
134
+ // deploy, which only needs one to wait.
135
+ const whoamiIfAny = function (opts) {
136
+ return whoami(opts).catch(() => null);
137
+ };
138
+
139
+ // [function]
140
+
141
+ // the release, as `--json` prints it.
142
+ const releaseJson = function (row) {
143
+ if (!row) {
144
+ return null;
145
+ }
146
+ return {
147
+ build: row.build,
148
+ channel: row.channel,
149
+ id: row.id,
150
+ message: row.message,
151
+ reason: row.reason,
152
+ status: row.status,
153
+ version: row.version,
154
+ };
155
+ };
156
+
157
+ // [function]
158
+
159
+ // waits on the release, then reports it: published, or failed or timed out,
160
+ // which exit 1. `result` is what the deploy prints.
161
+ const waitAndReport = async function (
162
+ current,
163
+ deployed,
164
+ result,
165
+ { git, json, quiet, root, since, timeout },
166
+ ) {
167
+ let sw = new SpinnerWheel(phrase.releaseWaiting(git));
168
+ sw.start(json || quiet);
169
+ let building = git;
170
+ let waited = null;
171
+ try {
172
+ waited = await wait(
173
+ current,
174
+ deployed,
175
+ { git, root, since, timeout },
176
+ {
177
+ onRelease() {
178
+ // found: GitHub Actions is done, and Arcadible publishes it now.
179
+ if (building) {
180
+ building = false;
181
+ sw.stop(true);
182
+ sw = new SpinnerWheel(phrase.releaseWaiting(false));
183
+ sw.start(json || quiet);
184
+ }
185
+ },
186
+ },
187
+ );
188
+ } catch (ex) {
189
+ sw.stop(true);
190
+ throw ex;
191
+ }
192
+ const { actions, release, studio, timedOut } = waited;
193
+ const data = { ...result, release: releaseJson(release) };
194
+ if (timedOut) {
195
+ const found = Boolean(release);
196
+ const reason = phrase.releaseTimedOut(result.version.to, timeout, {
197
+ actions,
198
+ found,
199
+ git,
200
+ studio,
201
+ });
202
+ abort("Wait timed out.", new Error(reason), { data, json, sw });
203
+ return;
204
+ }
205
+ if (release.status === "failed") {
206
+ const reason = phrase.releaseFailed(release, studio);
207
+ abort("Release failed.", new Error(reason), { data, json, sw });
208
+ return;
209
+ }
210
+ sw.stop(true);
211
+ if (json) {
212
+ print(data);
213
+ } else if (!quiet) {
214
+ report(result, { git, published: true });
215
+ }
216
+ };
217
+
218
+ // [function]
219
+
101
220
  const start = function (argv) {
102
221
  const git = argv.method === "git";
103
222
  const json = argv.json;
104
223
  const quiet = argv.quiet;
224
+ const timeout = argv.timeout ?? TIMEOUT[git ? "git" : "direct"];
105
225
  const root = resolve(process.cwd(), argv.cwd);
106
226
  let pb = null;
107
227
  let sw = new SpinnerWheel("Validating... %s");
228
+ sw.start(json || quiet);
108
229
  return validate({ root })
109
230
  .then((issues) => {
110
231
  const count = issues.length;
111
232
  if (count) {
112
233
  throw new Error(phrase.issuesFound(count));
113
234
  }
114
- sw.stop();
235
+ sw.stop(true);
115
236
  sw = new SpinnerWheel("Checking... %s");
237
+ // git deploys need no login: anyone who can push to the repository can
238
+ // release, and the Arcadible GitHub App publishes the release. Waiting
239
+ // reads the release, which needs the game's owner, so a git deploy
240
+ // that isn't logged in just doesn't wait.
241
+ if (git) {
242
+ return argv.wait
243
+ ? whoamiIfAny({ handler: handler({ json, pb, quiet, sw }) })
244
+ : null;
245
+ }
116
246
  return whoami({ handler: handler({ json, pb, quiet, sw }) });
117
247
  })
118
248
  .then((current) => {
119
- if (!current) {
249
+ if (!current && !git) {
120
250
  throw new Error(phrase.notLoggedIn());
121
251
  }
122
- sw.stop();
252
+ sw.stop(true);
123
253
  sw = new SpinnerWheel("Preparing... %s");
124
254
  return Promise.all([
125
255
  // whoami
@@ -134,7 +264,7 @@ const start = function (argv) {
134
264
  ]);
135
265
  })
136
266
  .then(([current]) => {
137
- sw.stop();
267
+ sw.stop(true);
138
268
  sw = new SpinnerWheel("Building... %s");
139
269
  sw.start(git || json || quiet);
140
270
  return Promise.all([
@@ -145,7 +275,7 @@ const start = function (argv) {
145
275
  ]);
146
276
  })
147
277
  .then(([current, artifact]) => {
148
- sw.stop();
278
+ sw.stop(true);
149
279
  sw = new SpinnerWheel("Packing... %s");
150
280
  sw.start(git || json || quiet);
151
281
  return Promise.all([
@@ -153,64 +283,69 @@ const start = function (argv) {
153
283
  current,
154
284
  // pack
155
285
  git ? null : pack({ input: artifact?.path, root }),
286
+ // build: whether workflow.build ran
287
+ Boolean(artifact),
156
288
  ]);
157
289
  })
158
- .then(([current, artifact]) => {
290
+ .then(([current, artifact, built]) => {
159
291
  sw.stop(true);
160
292
  if (!json && !quiet && !git) {
161
- pb = new ProgressBar(artifact.size);
293
+ pb = new ProgressBar(artifact.size, SUBMITTING);
162
294
  }
163
- return deploy({
164
- artifact,
295
+ return Promise.all([
296
+ // whoami
165
297
  current,
166
- git,
167
- handler: handler({ json, pb, quiet, sw }),
168
- root,
169
- });
298
+ // deploy
299
+ deploy({
300
+ artifact,
301
+ current,
302
+ git,
303
+ handler: handler({ json, pb, quiet, sw }),
304
+ root,
305
+ }),
306
+ built,
307
+ // when the release started, to find a git one by
308
+ Date.now(),
309
+ ]);
170
310
  })
171
- .then((version) => {
172
- if (json) {
173
- print({ version });
174
- return;
311
+ .then(([current, deployed, built, since]) => {
312
+ if (pb) {
313
+ pb.stop();
175
314
  }
176
- if (!quiet) {
177
- if (pb) {
178
- sw.stop();
179
- pb.stop();
180
- } else {
181
- sw.stop(true);
182
- }
183
- log.hs("Done.");
184
- if (version) {
185
- log.p(`Build v${version.to} submitted.`);
186
- return;
187
- }
188
- log.p("Nothing to do.");
315
+ // the game and release are for waiting, not printing.
316
+ const shown = { ...deployed };
317
+ delete shown.game;
318
+ delete shown.release;
319
+ const result = git ? shown : { built, ...shown };
320
+ sw.stop(true);
321
+ if (argv.wait && current) {
322
+ return waitAndReport(current, deployed, result, {
323
+ git,
324
+ json,
325
+ quiet,
326
+ root,
327
+ since,
328
+ timeout,
329
+ });
189
330
  }
190
- })
191
- .catch((ex) => {
192
- const message = ex.message;
193
331
  if (json) {
194
- print({ error: { message } });
195
- } else {
196
- if (pb) {
197
- sw.stop();
198
- pb.stop();
199
- } else {
200
- sw.stop(true);
201
- }
202
- log.hd("Deploy aborted.");
203
- log.p(message);
332
+ print(result);
333
+ } else if (!quiet) {
334
+ report(result, {
335
+ git,
336
+ note: argv.wait ? phrase.releaseNotWaited() : null,
337
+ });
204
338
  }
205
- process.exit(1);
206
- });
339
+ return null;
340
+ })
341
+ .catch((ex) => abort("Deploy aborted.", ex, { json, pb, sw }));
207
342
  };
208
343
 
209
344
  // [function]
210
345
 
211
346
  const main = function (argv) {
212
347
  return start(
213
- yargs(hideBin(argv))
348
+ yargs(args.normalize(hideBin(argv)))
214
349
  .scriptName(`${TOOL} ${COMMAND}`)
215
350
  .usage("$0 [method] [options]")
216
351
  .command("$0 [method]", false, (cli) =>
@@ -233,12 +368,17 @@ const main = function (argv) {
233
368
  describe: "Output only errors",
234
369
  type: "boolean",
235
370
  })
236
- .option("cwd", {
237
- alias: "C",
238
- default: ".",
239
- describe: "Set the working directory",
240
- type: "string",
371
+ .option("wait", {
372
+ alias: "w",
373
+ default: conf.wait,
374
+ describe: "Wait until it's published or fails",
375
+ type: "boolean",
376
+ })
377
+ .option("timeout", {
378
+ describe: "Seconds to wait [default: 300, or 900 for git]",
379
+ type: "number",
241
380
  })
381
+ .option("cwd", args.CWD)
242
382
  .help("help")
243
383
  .alias("help", "h")
244
384
  .version(VERSION)
@@ -248,7 +388,7 @@ const main = function (argv) {
248
388
  );
249
389
  };
250
390
 
251
- // [main]
391
+ // [start]
252
392
 
253
393
  if (require.main === module) {
254
394
  main(process.argv);