arcadible 0.1.33 → 0.1.34

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/package.json CHANGED
@@ -75,5 +75,5 @@
75
75
  "scripts": {
76
76
  "test": "node --test \"test/**/*.test.js\""
77
77
  },
78
- "version": "0.1.33"
78
+ "version": "0.1.34"
79
79
  }
@@ -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
- const findGit = function (game, version, since, headers) {
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
- api
74
- .gameReleases(game, { limit: 10, select: SELECT, version }, headers)
75
- .then(
76
- (rows) =>
77
- (rows ?? []).find(
78
- (r) =>
79
- r.source === "github" && Date.parse(r.created_at) >= since - SKEW,
80
- ) ?? null,
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(game, deployed.version.to, since, headers)
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 release's checks: its tag is free, and Arcadible would publish it.
82
- // resolves with the version, and the game's owner, who alone can wait for
83
- // the release.
84
- const ensureRelease = function (root, game, handler) {
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) => ({ owner, to: game.version }));
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,
@@ -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
 
@@ -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). The CLI never
63
- // reports them, but they share the Errors page.
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 = { build: conf.scripts?.build };
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
- `Waited ${waited(seconds)} for v${version}.`,
470
- found || !git
471
- ? [`It may still finish. See it in ${TITLE} Studio:`, studio]
472
- : [
473
- "GitHub Actions hasn't released it yet, or its workflow failed:",
474
- actions ?? "See the repository's Actions tab.",
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
  };