ravensight-playtest 0.1.0
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/LICENSE +21 -0
- package/README.md +380 -0
- package/addons/ravensight_driver/driver.gd +836 -0
- package/addons/ravensight_driver/export_plugin.gd +51 -0
- package/addons/ravensight_driver/plugin.cfg +7 -0
- package/addons/ravensight_driver/plugin.gd +36 -0
- package/bin/ravensight-playtest.js +31 -0
- package/package.json +45 -0
- package/src/api/README.md +500 -0
- package/src/api/client.js +340 -0
- package/src/api/errors.js +115 -0
- package/src/api/http.js +194 -0
- package/src/api/index.js +107 -0
- package/src/auth/deviceCode.js +79 -0
- package/src/auth/keychain.js +159 -0
- package/src/auth/session.js +128 -0
- package/src/cli.js +335 -0
- package/src/commands/brief.js +303 -0
- package/src/commands/check.js +318 -0
- package/src/commands/fakeCore.js +379 -0
- package/src/commands/init.js +120 -0
- package/src/commands/login.js +90 -0
- package/src/commands/logout.js +70 -0
- package/src/commands/open.js +125 -0
- package/src/commands/profile.js +262 -0
- package/src/commands/resume.js +156 -0
- package/src/commands/run.js +1015 -0
- package/src/commands/upload.js +137 -0
- package/src/config.js +100 -0
- package/src/dashboard.js +97 -0
- package/src/detect.js +77 -0
- package/src/errors.js +44 -0
- package/src/fsutil.js +77 -0
- package/src/godot.js +85 -0
- package/src/packs/index.js +191 -0
- package/src/paths.js +129 -0
- package/src/run/aggregate.js +658 -0
- package/src/run/args.js +111 -0
- package/src/run/context.js +181 -0
- package/src/run/deps.js +184 -0
- package/src/run/drivers/driver.js +183 -0
- package/src/run/drivers/godot-observation.js +138 -0
- package/src/run/drivers/godot-project.js +475 -0
- package/src/run/drivers/godot-rpc.js +225 -0
- package/src/run/drivers/godot.js +587 -0
- package/src/run/drivers/index.js +52 -0
- package/src/run/drivers/web.js +385 -0
- package/src/run/exit.js +21 -0
- package/src/run/heartbeat.js +131 -0
- package/src/run/index.js +31 -0
- package/src/run/json.js +56 -0
- package/src/run/model.js +384 -0
- package/src/run/paths.js +88 -0
- package/src/run/personaLoop.js +871 -0
- package/src/run/profile.js +214 -0
- package/src/run/regenerate.js +149 -0
- package/src/run/repoTools.js +286 -0
- package/src/run/report.js +222 -0
- package/src/run/resume.js +272 -0
- package/src/run/secretScan.js +171 -0
- package/src/run/state.js +198 -0
- package/src/run/synthetic.js +206 -0
- package/src/run/tools.js +344 -0
- package/src/run/transcript.js +93 -0
- package/src/run/usage.js +115 -0
- package/src/state/index.js +105 -0
- package/src/states.js +104 -0
- package/src/ui/index.js +195 -0
- package/src/upload/allowlist.js +116 -0
- package/src/upload/index.js +467 -0
- package/src/upload/queue.js +114 -0
- package/src/version.js +63 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createContext } from '../api/index.js';
|
|
2
|
+
import { ApiError } from '../api/errors.js';
|
|
3
|
+
import { listStates, readState } from '../state/index.js';
|
|
4
|
+
import { isRunTerminal } from '../states.js';
|
|
5
|
+
import { withAuthHandling } from '../auth/session.js';
|
|
6
|
+
import { pending } from '../upload/queue.js';
|
|
7
|
+
import { jobLink } from '../dashboard.js';
|
|
8
|
+
import { ui } from '../ui/index.js';
|
|
9
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load the runner's resume entry point, if this build has one.
|
|
13
|
+
*
|
|
14
|
+
* cli-core owns `resume` and the journal; the persona loop that actually
|
|
15
|
+
* restarts a run belongs to `src/run/`. A dynamic import keeps the two
|
|
16
|
+
* independent: on a tree where the runner has not landed, `resume` still
|
|
17
|
+
* reports and still drains uploads, and says plainly that it cannot restart
|
|
18
|
+
* anything.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Promise<{resumeJob: Function}|null>}
|
|
21
|
+
*/
|
|
22
|
+
export async function loadRunner() {
|
|
23
|
+
try {
|
|
24
|
+
const mod = await import('../run/index.js');
|
|
25
|
+
return typeof mod.resumeJob === 'function' ? mod : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `ravensight-playtest resume [jobId]`
|
|
33
|
+
*
|
|
34
|
+
* With no job id, list what this repo has journals for. With one, reconcile the
|
|
35
|
+
* journal against the server and hand off to the runner.
|
|
36
|
+
*
|
|
37
|
+
* The server is the authority on what the job and its runs actually are: the
|
|
38
|
+
* journal says what this machine last saw, which after a kill is by definition
|
|
39
|
+
* out of date. A run the server considers terminal is never restarted, which is
|
|
40
|
+
* what stops a resume from paying for work that was already delivered.
|
|
41
|
+
*
|
|
42
|
+
* @param {string|undefined} jobId
|
|
43
|
+
* @param {Object} [flags]
|
|
44
|
+
* @param {Object} [deps]
|
|
45
|
+
* @returns {Promise<number>}
|
|
46
|
+
*/
|
|
47
|
+
export async function resume(jobId, flags = {}, deps = {}) {
|
|
48
|
+
const context = deps.context || await createContext({
|
|
49
|
+
apiUrl: flags.apiUrl,
|
|
50
|
+
repoRoot: flags.repoRoot,
|
|
51
|
+
requireAuth: true
|
|
52
|
+
});
|
|
53
|
+
return withAuthHandling(context, () => resumeWith(context, jobId, flags, deps));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function resumeWith(context, jobId, flags, deps) {
|
|
57
|
+
const repoRoot = context.repoRoot;
|
|
58
|
+
|
|
59
|
+
if (!jobId) {
|
|
60
|
+
const states = await listStates({ repoRoot });
|
|
61
|
+
if (flags.json) {
|
|
62
|
+
ui.json({ jobs: states.map(({ job_id: id, game_id: game, updated_at: at }) => ({ job_id: id, game_id: game, updated_at: at })) });
|
|
63
|
+
return ExitCode.OK;
|
|
64
|
+
}
|
|
65
|
+
if (states.length === 0) {
|
|
66
|
+
ui.info('No jobs to resume in this repo.');
|
|
67
|
+
return ExitCode.OK;
|
|
68
|
+
}
|
|
69
|
+
ui.info('Jobs this repo has a journal for:');
|
|
70
|
+
ui.table(states, [
|
|
71
|
+
{ key: 'job_id', label: 'JOB' },
|
|
72
|
+
{ key: 'game_id', label: 'GAME' },
|
|
73
|
+
{ key: 'updated_at', label: 'LAST TOUCHED' }
|
|
74
|
+
]);
|
|
75
|
+
ui.blank();
|
|
76
|
+
ui.info('ravensight-playtest resume <jobId>');
|
|
77
|
+
return ExitCode.OK;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const state = await readState(jobId, { repoRoot });
|
|
81
|
+
if (!state) {
|
|
82
|
+
throw new CliError(
|
|
83
|
+
`No journal for ${jobId} in this repo.`,
|
|
84
|
+
1,
|
|
85
|
+
{ hint: 'Run resume with no job id to see what is here.' }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const gameId = flags.game || state.game_id;
|
|
89
|
+
if (!gameId) throw new CliError(`The journal for ${jobId} does not say which game it belongs to.`);
|
|
90
|
+
|
|
91
|
+
let job;
|
|
92
|
+
try {
|
|
93
|
+
({ job } = await context.api.jobs.get(gameId, jobId));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
96
|
+
throw new CliError(
|
|
97
|
+
`The server does not have ${jobId} for ${gameId}.`,
|
|
98
|
+
1,
|
|
99
|
+
{ hint: 'It may have been deleted. The local journal can be removed.' }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { runs } = await context.api.runs.list(gameId, jobId);
|
|
106
|
+
const finished = runs.filter(run => isRunTerminal(run.state));
|
|
107
|
+
const unfinished = runs.filter(run => !finished.includes(run));
|
|
108
|
+
const owed = await pending(jobId, { repoRoot });
|
|
109
|
+
|
|
110
|
+
if (flags.json) {
|
|
111
|
+
ui.json({
|
|
112
|
+
job,
|
|
113
|
+
runs,
|
|
114
|
+
finished: finished.map(run => run.run_id),
|
|
115
|
+
unfinished: unfinished.map(run => run.run_id),
|
|
116
|
+
pending_uploads: owed,
|
|
117
|
+
runner_available: Boolean(await loadRunner())
|
|
118
|
+
});
|
|
119
|
+
return ExitCode.OK;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ui.info(`Job ${jobId} on ${gameId} is ${job.state}.`);
|
|
123
|
+
ui.table(runs, [
|
|
124
|
+
{ key: 'run_id', label: 'RUN' },
|
|
125
|
+
{ key: 'persona', label: 'PERSONA' },
|
|
126
|
+
{ key: 'state', label: 'STATE' },
|
|
127
|
+
{ key: 'actions_taken', label: 'ACTIONS', align: 'right' }
|
|
128
|
+
]);
|
|
129
|
+
ui.blank();
|
|
130
|
+
ui.info(`${finished.length} runs finished, ${unfinished.length} to go.`);
|
|
131
|
+
if (owed.length > 0) ui.info(`${owed.length} files still to upload.`);
|
|
132
|
+
|
|
133
|
+
if (flags.status) {
|
|
134
|
+
ui.info(jobLink(gameId, jobId, context.apiUrl));
|
|
135
|
+
return ExitCode.OK;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const runner = deps.runner === undefined ? await loadRunner() : deps.runner;
|
|
139
|
+
if (!runner) {
|
|
140
|
+
ui.warn('This build has no run module, so there is nothing to restart.');
|
|
141
|
+
ui.info('Uploads can still be drained: ravensight-playtest upload ' + jobId);
|
|
142
|
+
return ExitCode.OK;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return runner.resumeJob({
|
|
146
|
+
api: context.api,
|
|
147
|
+
gameId,
|
|
148
|
+
jobId,
|
|
149
|
+
job,
|
|
150
|
+
runs,
|
|
151
|
+
state,
|
|
152
|
+
repoRoot,
|
|
153
|
+
flags
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|