arcadible 0.1.32 → 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.
Files changed (58) hide show
  1. package/README.md +4 -4
  2. package/bin/arc-build.js +23 -5
  3. package/bin/arc-deploy.js +78 -13
  4. package/bin/arc-init.js +6 -1
  5. package/bin/arc-inspect.js +1 -0
  6. package/bin/arc-login.js +4 -1
  7. package/bin/arc-open.js +4 -1
  8. package/bin/arc-pack.js +23 -3
  9. package/bin/arc-play.js +4 -1
  10. package/bin/arc-validate.js +1 -0
  11. package/bin/arc-whoami.js +1 -0
  12. package/package.json +3 -3
  13. package/src/account.js +7 -4
  14. package/src/action/build/tool.js +8 -4
  15. package/src/action/deploy/tool-git.js +49 -61
  16. package/src/action/deploy/tool.js +50 -44
  17. package/src/action/deploy/wait.js +37 -22
  18. package/src/action/init/manifest.js +23 -6
  19. package/src/action/init/tool-patch.js +1 -7
  20. package/src/action/init/tool.js +12 -9
  21. package/src/action/login/flow.js +7 -3
  22. package/src/action/pack/limits.js +13 -3
  23. package/src/action/pack/tool.js +19 -6
  24. package/src/action/play/tool.js +7 -1
  25. package/src/action/predeploy/ac.js +39 -22
  26. package/src/action/predeploy/git.js +69 -58
  27. package/src/action/predeploy/pm.js +24 -17
  28. package/src/action/predeploy/published.js +15 -2
  29. package/src/action/predeploy/tool-git.js +74 -86
  30. package/src/action/predeploy/tool.js +7 -33
  31. package/src/action/validate/tool.js +5 -4
  32. package/src/api.js +14 -1
  33. package/src/cli/predeploy.js +1 -1
  34. package/src/config/code.js +112 -0
  35. package/src/config/url.js +2 -1
  36. package/src/parser/env.js +19 -3
  37. package/src/parser/given.js +17 -0
  38. package/src/parser/manifest.js +8 -1
  39. package/src/parser/media.js +5 -9
  40. package/src/parser/project.js +4 -1
  41. package/src/parser/readme-file.js +8 -1
  42. package/src/phrase.js +75 -7
  43. package/src/schema/check/game.js +5 -26
  44. package/src/schema/game.js +117 -210
  45. package/src/schema/message.js +46 -7
  46. package/src/schema/readme.js +8 -3
  47. package/src/schema/workflow.js +35 -15
  48. package/src/util/argv.js +33 -1
  49. package/src/util/delay.js +4 -1
  50. package/src/util/error.js +49 -0
  51. package/src/util/image.js +4 -30
  52. package/src/util/log.js +8 -0
  53. package/src/util/media.js +40 -0
  54. package/src/util/output.js +34 -4
  55. package/src/util/request.js +11 -2
  56. package/src/util/s3.js +37 -20
  57. package/src/util/schema.js +93 -30
  58. package/src/util/video.js +4 -27
@@ -3,10 +3,16 @@
3
3
  // [import]
4
4
 
5
5
  const api = require("../../api");
6
+ const cfgcode = require("../../config/code");
7
+ const error = require("../../util/error");
6
8
  const links = require("../../util/links");
7
9
  const phrase = require("../../phrase");
8
10
  const project = require("../../parser/project");
9
11
 
12
+ // [use]
13
+
14
+ const ArcError = error.ArcError;
15
+
10
16
  // [function]
11
17
 
12
18
  // the game, `select`ed, or null when the API can't be reached. It rejects
@@ -15,7 +21,7 @@ const project = require("../../parser/project");
15
21
  const lookup = function (id, select) {
16
22
  return api.discoverGame(id, { select }).catch((err) => {
17
23
  if (err?.status === 404) {
18
- throw new Error(phrase.gameNotFound(id));
24
+ throw new ArcError(cfgcode.GAME_NOT_FOUND, phrase.gameNotFound(id));
19
25
  }
20
26
  return null;
21
27
  });
@@ -3,56 +3,73 @@
3
3
  // [import]
4
4
 
5
5
  const api = require("../../api");
6
+ const cfgcode = require("../../config/code");
7
+ const error = require("../../util/error");
6
8
  const phrase = require("../../phrase");
7
9
  const published = require("./published");
8
10
 
11
+ // [use]
12
+
13
+ const ArcError = error.ArcError;
14
+
9
15
  // [function]
10
16
 
11
- const ensureGame = function (current, idOrHandle, id, cb) {
17
+ // fails when `idOrHandle` is another game's, or this game's on another
18
+ // account. A game that isn't on Arcadible yet passes.
19
+ const ensureGame = function (current, idOrHandle, id) {
12
20
  return api.discoverGame(idOrHandle).then(
13
21
  (game) => {
14
22
  // a token's account isn't known yet, so only the id is compared; the
15
23
  // server checks ownership when it authorizes the upload.
16
24
  const owned = current?.token || game.account === current?.user?.id;
17
- if (owned && game.id === id) {
18
- cb(null);
19
- return;
25
+ if (!owned || game.id !== id) {
26
+ throw new ArcError(cfgcode.GAME_EXISTS, phrase.gameExists(idOrHandle));
20
27
  }
21
- cb(new Error(phrase.gameExists(idOrHandle)));
22
28
  },
23
29
  (err) => {
24
- if (err.status === 404) {
25
- cb(null);
26
- return;
30
+ if (err.status !== 404) {
31
+ throw err;
27
32
  }
28
- cb(err);
29
33
  },
30
34
  );
31
35
  };
32
36
 
33
37
  // [function]
34
38
 
35
- const ensureId = function (current, { handle, id }, cb) {
36
- return ensureGame(current, id, id, (err) => {
37
- if (err || (handle ?? null) === null) {
38
- cb(err);
39
- return;
40
- }
41
- ensureGame(current, handle, id, cb);
42
- });
39
+ // a game without a handle has none to check.
40
+ const ensureHandle = function (current, { handle, id }) {
41
+ if ((handle ?? null) === null) {
42
+ return Promise.resolve(null);
43
+ }
44
+ return ensureGame(current, handle, id);
45
+ };
46
+
47
+ // [function]
48
+
49
+ // resolves with the game once its id, and its handle if it has one, are
50
+ // free or already its own.
51
+ const ensureId = function (current, game) {
52
+ return ensureGame(current, game.id, game.id)
53
+ .then(() => ensureHandle(current, game))
54
+ .then(() => game);
43
55
  };
44
56
 
45
57
  // [function]
46
58
 
47
- // fails when the version is already published.
48
- const ensureUnpublished = function (current, { id, version }, cb) {
59
+ // resolves with the game, or fails when its version is already published.
60
+ const ensureUnpublished = function (current, game) {
61
+ const { id, version } = game;
49
62
  return api
50
63
  .gameBuild(id, version, null, api.auth(current?.session?.access_token))
51
64
  .then(
52
- () => published.versionPublished(id, version),
53
- (err) => (err.status === 404 ? null : err),
65
+ () => published.refuse(id, version),
66
+ (err) => {
67
+ if (err.status !== 404) {
68
+ throw err;
69
+ }
70
+ },
54
71
  )
55
- .then(cb);
72
+ .then(() => game);
56
73
  };
57
74
 
58
75
  // [export]
@@ -6,22 +6,46 @@ const cp = require("node:child_process");
6
6
 
7
7
  // [import]
8
8
 
9
+ const cfgcode = require("../../config/code");
9
10
  const cfgproduct = require("../../config/product");
11
+ const error = require("../../util/error");
10
12
  const phrase = require("../../phrase");
11
13
 
12
14
  // [use]
13
15
 
16
+ const ArcError = error.ArcError;
14
17
  const execFile = cp.execFile;
15
18
 
16
19
  // [declare]
17
20
 
18
21
  const HANDLE = cfgproduct.handle;
19
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";
20
25
 
21
26
  // [function]
22
27
 
23
- const git = function (root, args, cb) {
24
- execFile("git", ["-C", root, ...args], cb);
28
+ // runs git in `root`, and resolves with its stdout.
29
+ const git = function (root, args) {
30
+ return new Promise((resolve, reject) => {
31
+ execFile("git", ["-C", root, ...args], (err, stdout) => {
32
+ if (err) {
33
+ reject(err);
34
+ return;
35
+ }
36
+ resolve(stdout);
37
+ });
38
+ });
39
+ };
40
+
41
+ // [function]
42
+
43
+ // a rejection handler that replaces git's error with `code` and `message`,
44
+ // keeping it as the cause.
45
+ const failWith = function (code, message) {
46
+ return (err) => {
47
+ throw new ArcError(code, message, { cause: err });
48
+ };
25
49
  };
26
50
 
27
51
  // [function]
@@ -36,90 +60,75 @@ const tagOf = function (version) {
36
60
  // fails on any change that isn't committed: staged, unstaged, or untracked.
37
61
  // ignored files don't count. The release is built from HEAD, so anything
38
62
  // uncommitted wouldn't be part of it.
39
- const ensureClean = function (root, cb) {
40
- git(root, ["status", "--porcelain"], (err, stdout) => {
41
- if (err) {
42
- cb(err);
43
- return;
44
- }
63
+ const ensureClean = function (root) {
64
+ return git(root, ["status", "--porcelain"]).then((stdout) => {
45
65
  if (stdout.trim()) {
46
- cb(new Error(phrase.gitChangesPresent()));
47
- return;
66
+ throw new ArcError(cfgcode.GIT_UNCOMMITTED, phrase.gitChangesPresent());
48
67
  }
49
- cb(null);
50
68
  });
51
69
  };
52
70
 
53
71
  // [function]
54
72
 
55
- const ensureRemote = function (root, cb) {
56
- git(root, ["ls-remote", "origin"], (err) => {
57
- if (err) {
58
- cb(new Error(phrase.gitRemoteMissing(), { cause: err }));
59
- return;
60
- }
61
- cb(null);
62
- });
73
+ const ensureRemote = function (root) {
74
+ return git(root, ["ls-remote", "origin"]).then(
75
+ () => null,
76
+ failWith(cfgcode.GIT_REMOTE_UNAVAILABLE, phrase.gitRemoteMissing()),
77
+ );
63
78
  };
64
79
 
65
80
  // [function]
66
81
 
67
- const ensureRepo = function (root, cb) {
68
- git(root, ["rev-parse", "--is-inside-work-tree"], (err) => {
69
- if (err) {
70
- cb(new Error(phrase.gitRepoMissing(), { cause: err }));
71
- return;
72
- }
73
- cb(null);
74
- });
82
+ const ensureRepo = function (root) {
83
+ return git(root, ["rev-parse", "--is-inside-work-tree"]).then(
84
+ () => null,
85
+ failWith(cfgcode.GIT_REPO_MISSING, phrase.gitRepoMissing()),
86
+ );
75
87
  };
76
88
 
77
89
  // [function]
78
90
 
79
- const hasTagLocal = function (root, tag, cb) {
80
- git(root, ["tag", "--list", tag], (err, stdout) => {
81
- if (err) {
82
- cb(err);
83
- return;
84
- }
85
- cb(null, Boolean(stdout.trim()));
86
- });
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
+ );
87
98
  };
88
99
 
89
100
  // [function]
90
101
 
91
- const hasTagRemote = function (root, tag, cb) {
92
- git(
93
- root,
94
- ["ls-remote", "--tags", "origin", `refs/tags/${tag}`],
95
- (err, stdout) => {
96
- if (err) {
97
- cb(new Error(phrase.gitRemoteMissing(), { cause: err }));
98
- return;
99
- }
100
- cb(null, Boolean(stdout.trim()));
101
- },
102
+ const hasTagLocal = function (root, tag) {
103
+ return git(root, ["tag", "--list", tag]).then((stdout) =>
104
+ Boolean(stdout.trim()),
105
+ );
106
+ };
107
+
108
+ // [function]
109
+
110
+ const hasTagRemote = function (root, tag) {
111
+ return git(root, ["ls-remote", "--tags", "origin", `refs/tags/${tag}`]).then(
112
+ (stdout) => Boolean(stdout.trim()),
113
+ failWith(cfgcode.GIT_REMOTE_UNAVAILABLE, phrase.gitRemoteMissing()),
102
114
  );
103
115
  };
104
116
 
105
117
  // [function]
106
118
 
107
119
  // fails when the version's tag exists, locally or on origin.
108
- const ensureTagFree = function (root, version, cb) {
120
+ const ensureTagFree = function (root, version) {
109
121
  const tag = tagOf(version);
110
- hasTagLocal(root, tag, (err, local) => {
111
- if (err || local) {
112
- cb(err ?? new Error(phrase.gitVersionExists(version)));
113
- return;
114
- }
115
- hasTagRemote(root, tag, (err, remote) => {
116
- if (err || remote) {
117
- cb(err ?? new Error(phrase.gitVersionExists(version)));
118
- return;
122
+ return hasTagLocal(root, tag)
123
+ .then((local) => local || hasTagRemote(root, tag))
124
+ .then((exists) => {
125
+ if (exists) {
126
+ throw new ArcError(
127
+ cfgcode.GIT_TAG_EXISTS,
128
+ phrase.gitVersionExists(version),
129
+ );
119
130
  }
120
- cb(null);
121
131
  });
122
- });
123
132
  };
124
133
 
125
134
  // [export]
@@ -129,6 +138,8 @@ module.exports = {
129
138
  ensureRemote,
130
139
  ensureRepo,
131
140
  ensureTagFree,
141
+ ensureWorkflow,
142
+ failWith,
132
143
  git,
133
144
  tagOf,
134
145
  };
@@ -7,33 +7,40 @@ const path = require("node:path");
7
7
 
8
8
  // [import]
9
9
 
10
+ const cfgcode = require("../../config/code");
11
+ const error = require("../../util/error");
10
12
  const phrase = require("../../phrase");
11
13
 
12
14
  // [use]
13
15
 
16
+ const ArcError = error.ArcError;
14
17
  const existsSync = fs.existsSync; // eslint-disable-line no-sync
15
18
  const join = path.join;
16
19
 
20
+ // [declare]
21
+
22
+ const LOCK_FILES = [
23
+ // npm
24
+ "package-lock.json",
25
+ "npm-shrinkwrap.json",
26
+ // pnpm
27
+ "pnpm-lock.yaml",
28
+ // yarn
29
+ "yarn.lock",
30
+ ];
31
+
17
32
  // [function]
18
33
 
19
- const ensureLockFile = function (root, cb) {
20
- if (!existsSync(join(root, "package.json"))) {
21
- cb(null);
22
- return;
23
- }
24
- if (
25
- // npm
26
- existsSync(join(root, "package-lock.json")) ||
27
- existsSync(join(root, "npm-shrinkwrap.json")) ||
28
- // pnpm
29
- existsSync(join(root, "pnpm-lock.yaml")) ||
30
- // yarn
31
- existsSync(join(root, "yarn.lock"))
32
- ) {
33
- cb(null);
34
- return;
34
+ // fails when there's a package.json without a lockfile: the release workflow
35
+ // installs exactly what the lockfile pins.
36
+ const ensureLockFile = function (root) {
37
+ const has = (file) => existsSync(join(root, file));
38
+ if (has("package.json") && !LOCK_FILES.some(has)) {
39
+ return Promise.reject(
40
+ new ArcError(cfgcode.LOCKFILE_MISSING, phrase.noLockFile()),
41
+ );
35
42
  }
36
- cb(new Error(phrase.noLockFile()));
43
+ return Promise.resolve();
37
44
  };
38
45
 
39
46
  // [export]
@@ -7,11 +7,14 @@ const semver = require("semver");
7
7
  // [import]
8
8
 
9
9
  const api = require("../../api");
10
+ const cfgcode = require("../../config/code");
11
+ const error = require("../../util/error");
10
12
  const links = require("../../util/links");
11
13
  const phrase = require("../../phrase");
12
14
 
13
15
  // [use]
14
16
 
17
+ const ArcError = error.ArcError;
15
18
  const inc = semver.inc;
16
19
  const prerelease = semver.prerelease;
17
20
  const rcompare = semver.rcompare;
@@ -55,7 +58,8 @@ const versionPublished = function (id, version) {
55
58
  latest(id, links.STAGING),
56
59
  ]).then(([production, staging]) => {
57
60
  const found = { production, staging };
58
- return new Error(
61
+ return new ArcError(
62
+ cfgcode.VERSION_EXISTS,
59
63
  phrase.versionPublished(version, {
60
64
  ...found,
61
65
  next: next(version, found),
@@ -64,6 +68,15 @@ const versionPublished = function (id, version) {
64
68
  });
65
69
  };
66
70
 
71
+ // [function]
72
+
73
+ // rejects with versionPublished's error.
74
+ const refuse = function (id, version) {
75
+ return versionPublished(id, version).then((err) => {
76
+ throw err;
77
+ });
78
+ };
79
+
67
80
  // [export]
68
81
 
69
- module.exports = { next, versionPublished };
82
+ module.exports = { next, refuse };
@@ -3,28 +3,33 @@
3
3
  // [import]
4
4
 
5
5
  const api = require("../../api");
6
+ const cfgcode = require("../../config/code");
7
+ const cfguri = require("../../config/uri");
8
+ const error = require("../../util/error");
6
9
  const git = require("./git");
10
+ const given = require("../../parser/given");
7
11
  const phrase = require("../../phrase");
8
12
  const pm = require("./pm");
9
- const project = require("../../parser/project");
10
13
  const published = require("./published");
11
14
 
12
15
  // [use]
13
16
 
17
+ const ArcError = error.ArcError;
14
18
  const ensureClean = git.ensureClean;
15
19
  const ensureLockFile = pm.ensureLockFile;
16
20
  const ensureRemote = git.ensureRemote;
17
21
  const ensureRepo = git.ensureRepo;
18
22
  const ensureTagFree = git.ensureTagFree;
23
+ const ensureWorkflow = git.ensureWorkflow;
19
24
 
20
25
  // [function]
21
26
 
22
- // a public lookup's answer: found (200), missing (404), or null for any
23
- // other answer, such as when the API can't be reached.
27
+ // a public lookup's answer: found (200, with its data), missing (404), or
28
+ // null for any other answer, such as when the API can't be reached.
24
29
  const lookup = function (request) {
25
30
  return request.then(
26
- () => 200,
27
- (err) => (err.status === 404 ? 404 : null),
31
+ (data) => ({ data, status: 200 }),
32
+ (err) => (err.status === 404 ? { status: 404 } : null),
28
33
  );
29
34
  };
30
35
 
@@ -35,113 +40,96 @@ const lookup = function (request) {
35
40
  // that's already published. The lookups are public, so this needs no login.
36
41
  // when Arcadible can't be reached, it warns and pushes anyway: a release
37
42
  // shouldn't depend on the API, and Arcadible still checks.
38
- const ensurePublishable = function ({ handle, id, version }, handler, cb) {
43
+ // resolves with the game's owner, or null when Arcadible can't be reached.
44
+ const ensurePublishable = function ({ handle, id, version }, handler) {
39
45
  return Promise.all([
40
46
  lookup(api.discoverGame(id)),
41
47
  lookup(api.discoverGameBuild(id, version)),
42
- ])
43
- .then(([game, build]) => {
44
- if (game === 404) {
45
- return new Error(phrase.gameNotDeployed(handle ?? id));
46
- }
47
- if (game === null || build === null) {
48
- if (handler.onLogInfo) {
49
- handler.onLogInfo({
50
- level: "warn",
51
- message: phrase.versionUnchecked(version),
52
- });
53
- }
54
- return null;
55
- }
56
- return build === 200 ? published.versionPublished(id, version) : null;
57
- })
58
- .then(cb);
59
- };
60
-
61
- // [function]
62
-
63
- const deployRelease = function (game, { root }, handler, cb) {
64
- ensureTagFree(root, game.version, (err) => {
65
- if (err) {
66
- cb(err);
67
- return;
48
+ ]).then(([game, build]) => {
49
+ if (game?.status === 404) {
50
+ throw new ArcError(
51
+ cfgcode.GAME_NOT_DEPLOYED,
52
+ phrase.gameNotDeployed(handle ?? id),
53
+ );
68
54
  }
69
- ensurePublishable(game, handler, (err) => {
70
- if (err) {
71
- cb(err);
72
- return;
55
+ if (game === null || build === null) {
56
+ if (handler.onLogInfo) {
57
+ handler.onLogInfo({
58
+ level: "warn",
59
+ message: phrase.versionUnchecked(version),
60
+ });
73
61
  }
74
- cb(null, { to: game.version });
75
- });
76
- });
77
- };
78
-
79
- // [function]
80
-
81
- const deployEnsureGit = function ({ root }, handler, cb) {
82
- ensureRepo(root, (err) => {
83
- if (err) {
84
- cb(err);
85
- return;
62
+ return null;
86
63
  }
87
- ensureClean(root, (err) => {
88
- if (err) {
89
- cb(err);
90
- return;
91
- }
92
- ensureRemote(root, cb);
93
- });
64
+ return build.status === 200
65
+ ? published.refuse(id, version)
66
+ : (game.data?.account ?? null);
94
67
  });
95
68
  };
96
69
 
97
70
  // [function]
98
71
 
99
- const deployEnsurePm = function ({ root }, handler, cb) {
100
- ensureLockFile(root, cb);
72
+ // the project's checks: a lockfile when there's a package.json, and a clean
73
+ // repository with the release workflow and a reachable origin.
74
+ const ensureProject = function (root) {
75
+ return ensureLockFile(root)
76
+ .then(() => ensureRepo(root))
77
+ .then(() => ensureClean(root))
78
+ .then(() => ensureWorkflow(root))
79
+ .then(() => ensureRemote(root));
101
80
  };
102
81
 
103
82
  // [function]
104
83
 
105
- const deployEnsure = function (conf, handler, cb) {
106
- deployEnsurePm(conf, handler, (err) => {
107
- if (err) {
108
- cb(err);
109
- return;
110
- }
111
- deployEnsureGit(conf, handler, cb);
112
- });
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
+ );
113
107
  };
114
108
 
115
109
  // [function]
116
110
 
117
- const deploy = function (game, conf, handler, cb) {
118
- deployEnsure(conf, handler, (err) => {
119
- if (err) {
120
- cb(err);
121
- return;
122
- }
123
- deployRelease(game, conf, handler, cb);
124
- });
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) {
115
+ return ensureTagFree(root, game.version)
116
+ .then(() => ensurePublishable(game, handler))
117
+ .then((owner) =>
118
+ Promise.all([owner, ensureConnected(current, game, owner)]),
119
+ )
120
+ .then(([owner]) => ({ owner, to: game.version }));
125
121
  };
126
122
 
127
123
  // [function]
128
124
 
129
- const main = function ({ root }, { onPredeployBegin, ...handler }) {
125
+ const main = function (current, conf, { onPredeployBegin, ...handler }) {
126
+ const root = conf.root;
130
127
  if (onPredeployBegin) {
131
128
  onPredeployBegin();
132
129
  }
133
- return project(root).then(
134
- ({ game }) =>
135
- new Promise((resolve, reject) => {
136
- deploy(game, { root }, handler, (err, data) => {
137
- if (err) {
138
- reject(err);
139
- return;
140
- }
141
- resolve(data);
142
- });
143
- }),
144
- );
130
+ return given(conf)
131
+ .then(({ game }) => Promise.all([game, ensureProject(root)]))
132
+ .then(([game]) => ensureRelease(root, current, game, handler));
145
133
  };
146
134
 
147
135
  // [export]