arcadible 0.1.39 → 0.1.41
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/README.md +1 -1
- package/package.json +1 -1
- package/src/action/predeploy/git.js +18 -0
- package/src/action/predeploy/tool-git.js +4 -1
- package/src/config/code.js +3 -0
- package/src/parser/project.js +63 -3
- package/src/phrase.js +22 -0
- package/src/schema/workflow.js +44 -31
- package/src/util/schema.js +7 -0
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -95,6 +95,23 @@ const ensureRepo = function (root) {
|
|
|
95
95
|
|
|
96
96
|
// [function]
|
|
97
97
|
|
|
98
|
+
// fails when the project isn't the repository's top level: the release
|
|
99
|
+
// workflow builds from the root, so a project in a directory below it would
|
|
100
|
+
// fail on GitHub, or release the root's game.
|
|
101
|
+
const ensureRoot = function (root) {
|
|
102
|
+
return git(root, ["rev-parse", "--show-prefix"]).then((stdout) => {
|
|
103
|
+
const prefix = stdout.trim();
|
|
104
|
+
if (prefix) {
|
|
105
|
+
throw new ArcError(
|
|
106
|
+
cfgcode.PROJECT_NOT_REPO_ROOT,
|
|
107
|
+
phrase.projectNotRepoRoot(prefix),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// [function]
|
|
114
|
+
|
|
98
115
|
// fails when HEAD has no release workflow, which Arcadible checks for when the
|
|
99
116
|
// tag arrives (workflow_missing).
|
|
100
117
|
const ensureWorkflow = function (root) {
|
|
@@ -178,6 +195,7 @@ module.exports = {
|
|
|
178
195
|
ensureClean,
|
|
179
196
|
ensureRemote,
|
|
180
197
|
ensureRepo,
|
|
198
|
+
ensureRoot,
|
|
181
199
|
ensureTagFree,
|
|
182
200
|
ensureWorkflow,
|
|
183
201
|
failWith,
|
|
@@ -19,6 +19,7 @@ const ensureClean = git.ensureClean;
|
|
|
19
19
|
const ensureLockFile = pm.ensureLockFile;
|
|
20
20
|
const ensureRemote = git.ensureRemote;
|
|
21
21
|
const ensureRepo = git.ensureRepo;
|
|
22
|
+
const ensureRoot = git.ensureRoot;
|
|
22
23
|
const ensureTagFree = git.ensureTagFree;
|
|
23
24
|
const ensureWorkflow = git.ensureWorkflow;
|
|
24
25
|
const originRepo = git.originRepo;
|
|
@@ -71,10 +72,12 @@ const ensurePublishable = function ({ handle, id, version }, handler) {
|
|
|
71
72
|
// [function]
|
|
72
73
|
|
|
73
74
|
// the project's checks: a lockfile when there's a package.json, and a clean
|
|
74
|
-
// repository with the release workflow and a
|
|
75
|
+
// repository, whose root is the project, with the release workflow and a
|
|
76
|
+
// reachable origin.
|
|
75
77
|
const ensureProject = function (root) {
|
|
76
78
|
return ensureLockFile(root)
|
|
77
79
|
.then(() => ensureRepo(root))
|
|
80
|
+
.then(() => ensureRoot(root))
|
|
78
81
|
.then(() => ensureClean(root))
|
|
79
82
|
.then(() => ensureWorkflow(root))
|
|
80
83
|
.then(() => ensureRemote(root));
|
package/src/config/code.js
CHANGED
|
@@ -24,6 +24,7 @@ const CLI = {
|
|
|
24
24
|
LOCKFILE_MISSING: "lockfile_missing",
|
|
25
25
|
LOGIN_EXPIRED: "login_expired",
|
|
26
26
|
LOGIN_FAILED: "login_failed",
|
|
27
|
+
MANIFEST_MISSING: "manifest_missing",
|
|
27
28
|
NETWORK_ERROR: "network_error",
|
|
28
29
|
NOT_LOGGED_IN: "not_logged_in",
|
|
29
30
|
PACKAGE_FILE_TOO_LARGE: "package_file_too_large",
|
|
@@ -33,6 +34,7 @@ const CLI = {
|
|
|
33
34
|
PACKAGE_UNCOMPRESSED_TOO_LARGE: "package_uncompressed_too_large",
|
|
34
35
|
PARSE_FAILED: "parse_failed",
|
|
35
36
|
PATH_NOT_FOUND: "path_not_found",
|
|
37
|
+
PROJECT_NOT_REPO_ROOT: "project_not_repo_root",
|
|
36
38
|
SESSION_EXPIRED: "session_expired",
|
|
37
39
|
TEMPLATE_MISSING: "template_missing",
|
|
38
40
|
TEMPLATE_UNAVAILABLE: "template_unavailable",
|
|
@@ -48,6 +50,7 @@ const CLI = {
|
|
|
48
50
|
const RELEASE = {
|
|
49
51
|
APP_NOT_INSTALLED: "app_not_installed",
|
|
50
52
|
EXPIRED: "expired",
|
|
53
|
+
GAME_LIMIT_REACHED: "game_limit_reached",
|
|
51
54
|
GAME_MISMATCH: "game_mismatch",
|
|
52
55
|
GAME_UNAVAILABLE: "game_unavailable",
|
|
53
56
|
INTERNAL: "internal",
|
package/src/parser/project.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// [import]
|
|
4
4
|
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
// [import]
|
|
8
|
+
|
|
5
9
|
const fs = require("fs-extra");
|
|
6
10
|
|
|
7
11
|
// [import]
|
|
8
12
|
|
|
9
13
|
const cfgcode = require("../config/code");
|
|
14
|
+
const cfgproduct = require("../config/product");
|
|
10
15
|
const cfgworkflow = require("../config/workflow");
|
|
11
16
|
const env = require("./env");
|
|
12
17
|
const error = require("../util/error");
|
|
@@ -19,7 +24,16 @@ const readme = require("./readme");
|
|
|
19
24
|
// [use]
|
|
20
25
|
|
|
21
26
|
const ArcError = error.ArcError;
|
|
27
|
+
const dirname = path.dirname;
|
|
28
|
+
const isAbsolute = path.isAbsolute;
|
|
29
|
+
const join = path.join;
|
|
22
30
|
const pathExists = fs.pathExists;
|
|
31
|
+
const relative = path.relative;
|
|
32
|
+
const sep = path.sep;
|
|
33
|
+
|
|
34
|
+
// [declare]
|
|
35
|
+
|
|
36
|
+
const MANIFEST = `${cfgproduct.handle}.toml`;
|
|
23
37
|
|
|
24
38
|
// [function]
|
|
25
39
|
|
|
@@ -44,6 +58,51 @@ const configWorkflow = function (envWorkflow, manifestWorkflow) {
|
|
|
44
58
|
|
|
45
59
|
// [function]
|
|
46
60
|
|
|
61
|
+
// a directory as the developer would type it: relative to where arc runs
|
|
62
|
+
// when it's there, below it, or above it (`..`), or else its full path.
|
|
63
|
+
const shown = function (dir) {
|
|
64
|
+
const rel = relative(process.cwd(), dir);
|
|
65
|
+
const above = rel.split(sep).every((part) => part === "..");
|
|
66
|
+
return isAbsolute(rel) || (rel.startsWith("..") && !above) ? dir : rel || ".";
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// [function]
|
|
70
|
+
|
|
71
|
+
// the nearest directory above `dir` with a manifest, or null. It's only
|
|
72
|
+
// named in the error's hint: arc never reads a project it wasn't given.
|
|
73
|
+
const nearestAbove = function (dir) {
|
|
74
|
+
const parent = dirname(dir);
|
|
75
|
+
if (parent === dir) {
|
|
76
|
+
return Promise.resolve(null);
|
|
77
|
+
}
|
|
78
|
+
return pathExists(join(parent, MANIFEST)).then((exists) => {
|
|
79
|
+
if (exists) {
|
|
80
|
+
return parent;
|
|
81
|
+
}
|
|
82
|
+
return nearestAbove(parent);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// [function]
|
|
87
|
+
|
|
88
|
+
// fails when `root` has no manifest. A game's id is only ever in it, so a
|
|
89
|
+
// project without one can't be deployed, and the likeliest cause is running
|
|
90
|
+
// arc from inside a project: the hint names the project above, if any.
|
|
91
|
+
const ensureManifest = function (root) {
|
|
92
|
+
return pathExists(join(root, MANIFEST))
|
|
93
|
+
.then((exists) => (exists ? false : nearestAbove(root)))
|
|
94
|
+
.then((found) => {
|
|
95
|
+
if (found !== false) {
|
|
96
|
+
throw new ArcError(
|
|
97
|
+
cfgcode.MANIFEST_MISSING,
|
|
98
|
+
phrase.noManifest(MANIFEST, shown(root), found && shown(found)),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// [function]
|
|
105
|
+
|
|
47
106
|
// the other sources, once arcadible.toml is read: the README's title is read
|
|
48
107
|
// word for word when arcadible.toml sets the short title.
|
|
49
108
|
const parseSources = function (root, manifestData) {
|
|
@@ -66,16 +125,17 @@ const parseSources = function (root, manifestData) {
|
|
|
66
125
|
|
|
67
126
|
// [function]
|
|
68
127
|
|
|
69
|
-
// the project at `root`, which must exist: a mistyped
|
|
70
|
-
// read as an empty project.
|
|
128
|
+
// the project at `root`, which must exist, with its manifest: a mistyped
|
|
129
|
+
// -C would otherwise read as an empty project.
|
|
71
130
|
const main = function (root) {
|
|
72
131
|
return pathExists(root)
|
|
73
132
|
.then((exists) => {
|
|
74
133
|
if (!exists) {
|
|
75
134
|
throw new ArcError(cfgcode.PATH_NOT_FOUND, phrase.unableToFind(root));
|
|
76
135
|
}
|
|
77
|
-
return
|
|
136
|
+
return ensureManifest(root);
|
|
78
137
|
})
|
|
138
|
+
.then(() => manifest(root))
|
|
79
139
|
.then((manifestData) => parseSources(root, manifestData));
|
|
80
140
|
};
|
|
81
141
|
|
package/src/phrase.js
CHANGED
|
@@ -214,6 +214,26 @@ const gitRemoteMissing = function () {
|
|
|
214
214
|
|
|
215
215
|
// [function]
|
|
216
216
|
|
|
217
|
+
const noManifest = function (file, dir, found) {
|
|
218
|
+
return blocks(
|
|
219
|
+
dir === "." ? `No ${file} in this directory.` : `No ${file} in ${dir}`,
|
|
220
|
+
found
|
|
221
|
+
? `The project is in \`${found}\`: run arc there, or pass \`-C ${found}\`.`
|
|
222
|
+
: "Run arc in the project's directory, or pass -C, or start a project with `arc init`.",
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// [function]
|
|
227
|
+
|
|
228
|
+
const projectNotRepoRoot = function (prefix) {
|
|
229
|
+
return blocks(
|
|
230
|
+
`arc deploy git releases the project at the repository's root, and this one is in ${prefix}`,
|
|
231
|
+
"Deploy it with `arc deploy`, or give it a repository of its own.",
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// [function]
|
|
236
|
+
|
|
217
237
|
const gitRepoMissing = function () {
|
|
218
238
|
return blocks(
|
|
219
239
|
"No Git repository found.",
|
|
@@ -771,6 +791,7 @@ module.exports = {
|
|
|
771
791
|
loginFailed,
|
|
772
792
|
noGameId,
|
|
773
793
|
noLockFile,
|
|
794
|
+
noManifest,
|
|
774
795
|
noProductionRelease,
|
|
775
796
|
noTemplate,
|
|
776
797
|
noVideoMetadata,
|
|
@@ -783,6 +804,7 @@ module.exports = {
|
|
|
783
804
|
packageTooLarge,
|
|
784
805
|
packageTooManyFiles,
|
|
785
806
|
packageUncompressedTooLarge,
|
|
807
|
+
projectNotRepoRoot,
|
|
786
808
|
releaseFailed,
|
|
787
809
|
releasePublished,
|
|
788
810
|
releaseTimedOut,
|
package/src/schema/workflow.js
CHANGED
|
@@ -43,45 +43,57 @@ const hasBuild = function (build) {
|
|
|
43
43
|
|
|
44
44
|
// [function]
|
|
45
45
|
|
|
46
|
+
// a value of neither type says what the matching kind expected, a list's for
|
|
47
|
+
// a list and a string's otherwise, not the union's bare "Invalid input".
|
|
48
|
+
const typeMessage = function (issue) {
|
|
49
|
+
const branch = Array.isArray(issue.input) ? 1 : 0;
|
|
50
|
+
return issue.errors?.[branch]?.[0]?.message;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// [function]
|
|
54
|
+
|
|
46
55
|
const zBuild = function () {
|
|
47
|
-
|
|
48
|
-
return z.union([z.string(), z.array(z.string())]);
|
|
56
|
+
return z.union([z.string(), z.array(z.string())], { error: typeMessage });
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
// [function]
|
|
52
60
|
|
|
61
|
+
// a directory inside the project with an index.html. Each check aborts the
|
|
62
|
+
// rest when it fails, so one bad path gets one issue.
|
|
53
63
|
const zDeployDir = function (root, { build }) {
|
|
54
64
|
const field = "deploy_dir";
|
|
55
65
|
return z
|
|
56
66
|
.string()
|
|
57
|
-
.min(1,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
.min(1, {
|
|
68
|
+
...message(field, ["Must not be empty. Leave the field out instead."]),
|
|
69
|
+
abort: true,
|
|
70
|
+
})
|
|
71
|
+
.refine((file) => isRelativeCheck(root, file), {
|
|
72
|
+
...message(field, ["Must be a relative path."], ISSUE.PATH_INVALID),
|
|
73
|
+
abort: true,
|
|
74
|
+
})
|
|
75
|
+
.refine((file) => isInsideCheck(root, file), {
|
|
76
|
+
...message(field, ["Must be within the project."], ISSUE.PATH_INVALID),
|
|
77
|
+
abort: true,
|
|
78
|
+
})
|
|
79
|
+
.refine((file) => !isRootCheck(root, file), {
|
|
80
|
+
...message(
|
|
69
81
|
field,
|
|
70
82
|
["Must be a directory inside the project, not the project root."],
|
|
71
83
|
ISSUE.PATH_INVALID,
|
|
72
84
|
),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
message(
|
|
85
|
+
abort: true,
|
|
86
|
+
})
|
|
87
|
+
.refine((file) => avoidsCheck(root, file, MEDIA), {
|
|
88
|
+
...message(
|
|
77
89
|
field,
|
|
78
90
|
[`Must not contain ${MEDIA} in path.`],
|
|
79
91
|
ISSUE.PATH_INVALID,
|
|
80
92
|
),
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
message(
|
|
93
|
+
abort: true,
|
|
94
|
+
})
|
|
95
|
+
.refine((file) => hasBuild(build) || existsCheck(root, file), {
|
|
96
|
+
...message(
|
|
85
97
|
field,
|
|
86
98
|
[
|
|
87
99
|
"Must exist when no build step is defined.",
|
|
@@ -89,25 +101,26 @@ const zDeployDir = function (root, { build }) {
|
|
|
89
101
|
],
|
|
90
102
|
ISSUE.PATH_INVALID,
|
|
91
103
|
),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
message(
|
|
104
|
+
abort: true,
|
|
105
|
+
})
|
|
106
|
+
.refine((file) => hasFileCheck(root, file, INDEX), {
|
|
107
|
+
...message(
|
|
96
108
|
field,
|
|
97
109
|
[`Must have an ${INDEX} at its root.`],
|
|
98
110
|
ISSUE.INDEX_MISSING,
|
|
99
111
|
),
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
message(
|
|
112
|
+
abort: true,
|
|
113
|
+
})
|
|
114
|
+
.refine((file) => lacksFilesCheck(root, file, RESERVED), {
|
|
115
|
+
...message(
|
|
104
116
|
field,
|
|
105
117
|
[
|
|
106
118
|
`Must not have ${RESERVED.join(" or ")} at its root. They're reserved for the files ${cfgproduct.title} adds.`,
|
|
107
119
|
],
|
|
108
120
|
ISSUE.PATH_INVALID,
|
|
109
121
|
),
|
|
110
|
-
|
|
122
|
+
abort: true,
|
|
123
|
+
});
|
|
111
124
|
};
|
|
112
125
|
|
|
113
126
|
// [function]
|
package/src/util/schema.js
CHANGED
|
@@ -39,6 +39,13 @@ const codeOf = function (issue) {
|
|
|
39
39
|
return typeof issue.input === "undefined"
|
|
40
40
|
? ISSUE.REQUIRED
|
|
41
41
|
: ISSUE.TYPE_INVALID;
|
|
42
|
+
case "invalid_union":
|
|
43
|
+
// every branch refused the value's type, e.g. `build = 3`
|
|
44
|
+
return issue.errors.every((branch) =>
|
|
45
|
+
branch.every((each) => each.code === "invalid_type"),
|
|
46
|
+
)
|
|
47
|
+
? ISSUE.TYPE_INVALID
|
|
48
|
+
: ISSUE.INVALID;
|
|
42
49
|
case "invalid_value":
|
|
43
50
|
return ISSUE.OPTION_INVALID;
|
|
44
51
|
case "too_big":
|