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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/addons/ravensight_driver/driver.gd +836 -0
  4. package/addons/ravensight_driver/export_plugin.gd +51 -0
  5. package/addons/ravensight_driver/plugin.cfg +7 -0
  6. package/addons/ravensight_driver/plugin.gd +36 -0
  7. package/bin/ravensight-playtest.js +31 -0
  8. package/package.json +45 -0
  9. package/src/api/README.md +500 -0
  10. package/src/api/client.js +340 -0
  11. package/src/api/errors.js +115 -0
  12. package/src/api/http.js +194 -0
  13. package/src/api/index.js +107 -0
  14. package/src/auth/deviceCode.js +79 -0
  15. package/src/auth/keychain.js +159 -0
  16. package/src/auth/session.js +128 -0
  17. package/src/cli.js +335 -0
  18. package/src/commands/brief.js +303 -0
  19. package/src/commands/check.js +318 -0
  20. package/src/commands/fakeCore.js +379 -0
  21. package/src/commands/init.js +120 -0
  22. package/src/commands/login.js +90 -0
  23. package/src/commands/logout.js +70 -0
  24. package/src/commands/open.js +125 -0
  25. package/src/commands/profile.js +262 -0
  26. package/src/commands/resume.js +156 -0
  27. package/src/commands/run.js +1015 -0
  28. package/src/commands/upload.js +137 -0
  29. package/src/config.js +100 -0
  30. package/src/dashboard.js +97 -0
  31. package/src/detect.js +77 -0
  32. package/src/errors.js +44 -0
  33. package/src/fsutil.js +77 -0
  34. package/src/godot.js +85 -0
  35. package/src/packs/index.js +191 -0
  36. package/src/paths.js +129 -0
  37. package/src/run/aggregate.js +658 -0
  38. package/src/run/args.js +111 -0
  39. package/src/run/context.js +181 -0
  40. package/src/run/deps.js +184 -0
  41. package/src/run/drivers/driver.js +183 -0
  42. package/src/run/drivers/godot-observation.js +138 -0
  43. package/src/run/drivers/godot-project.js +475 -0
  44. package/src/run/drivers/godot-rpc.js +225 -0
  45. package/src/run/drivers/godot.js +587 -0
  46. package/src/run/drivers/index.js +52 -0
  47. package/src/run/drivers/web.js +385 -0
  48. package/src/run/exit.js +21 -0
  49. package/src/run/heartbeat.js +131 -0
  50. package/src/run/index.js +31 -0
  51. package/src/run/json.js +56 -0
  52. package/src/run/model.js +384 -0
  53. package/src/run/paths.js +88 -0
  54. package/src/run/personaLoop.js +871 -0
  55. package/src/run/profile.js +214 -0
  56. package/src/run/regenerate.js +149 -0
  57. package/src/run/repoTools.js +286 -0
  58. package/src/run/report.js +222 -0
  59. package/src/run/resume.js +272 -0
  60. package/src/run/secretScan.js +171 -0
  61. package/src/run/state.js +198 -0
  62. package/src/run/synthetic.js +206 -0
  63. package/src/run/tools.js +344 -0
  64. package/src/run/transcript.js +93 -0
  65. package/src/run/usage.js +115 -0
  66. package/src/state/index.js +105 -0
  67. package/src/states.js +104 -0
  68. package/src/ui/index.js +195 -0
  69. package/src/upload/allowlist.js +116 -0
  70. package/src/upload/index.js +467 -0
  71. package/src/upload/queue.js +114 -0
  72. 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
+