cyclops-clientdevbridge-cli 0.1.1
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/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/artifacts.d.ts +56 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +58 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compare.d.ts +27 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +209 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/context.d.ts +25 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +36 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +31 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +234 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/hotswap.d.ts +7 -0
- package/dist/commands/hotswap.d.ts.map +1 -0
- package/dist/commands/hotswap.js +76 -0
- package/dist/commands/hotswap.js.map +1 -0
- package/dist/commands/input.d.ts +53 -0
- package/dist/commands/input.d.ts.map +1 -0
- package/dist/commands/input.js +204 -0
- package/dist/commands/input.js.map +1 -0
- package/dist/commands/inspect.d.ts +19 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +90 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/lifecycle.d.ts +29 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +201 -0
- package/dist/commands/lifecycle.js.map +1 -0
- package/dist/commands/snapshot.d.ts +37 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +133 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/world.d.ts +27 -0
- package/dist/commands/world.d.ts.map +1 -0
- package/dist/commands/world.js +203 -0
- package/dist/commands/world.js.map +1 -0
- package/dist/detect.d.ts +58 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +185 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/hotswap.d.ts +58 -0
- package/dist/hotswap.d.ts.map +1 -0
- package/dist/hotswap.js +284 -0
- package/dist/hotswap.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/initscript.d.ts +49 -0
- package/dist/initscript.d.ts.map +1 -0
- package/dist/initscript.js +202 -0
- package/dist/initscript.js.map +1 -0
- package/dist/java.d.ts +47 -0
- package/dist/java.d.ts.map +1 -0
- package/dist/java.js +114 -0
- package/dist/java.js.map +1 -0
- package/dist/launcher.d.ts +104 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +609 -0
- package/dist/launcher.js.map +1 -0
- package/dist/loopback.d.ts +17 -0
- package/dist/loopback.d.ts.map +1 -0
- package/dist/loopback.js +19 -0
- package/dist/loopback.js.map +1 -0
- package/dist/output.d.ts +21 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +41 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +47 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/client.d.ts +35 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +170 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/types.d.ts +114 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +38 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +79 -0
- package/dist/session.js.map +1 -0
- package/dist/snapshot/model.d.ts +327 -0
- package/dist/snapshot/model.d.ts.map +1 -0
- package/dist/snapshot/model.js +91 -0
- package/dist/snapshot/model.js.map +1 -0
- package/dist/snapshot/outline.d.ts +35 -0
- package/dist/snapshot/outline.d.ts.map +1 -0
- package/dist/snapshot/outline.js +135 -0
- package/dist/snapshot/outline.js.map +1 -0
- package/dist/xvfb.d.ts +30 -0
- package/dist/xvfb.d.ts.map +1 -0
- package/dist/xvfb.js +66 -0
- package/dist/xvfb.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { line, printJson, printPath } from '../output.js';
|
|
2
|
+
import { findNodes, formatOutline } from '../snapshot/outline.js';
|
|
3
|
+
import { centre, label, simpleType, snapshotSchema } from '../snapshot/model.js';
|
|
4
|
+
import { CliError } from '../errors.js';
|
|
5
|
+
import { timestampName, withClient, writeBase64 } from './context.js';
|
|
6
|
+
export async function fetchSnapshot(client, options = {}) {
|
|
7
|
+
const params = {};
|
|
8
|
+
if (options.includeHidden === true) {
|
|
9
|
+
params['includeHidden'] = true;
|
|
10
|
+
}
|
|
11
|
+
if (options.maxDepth !== undefined) {
|
|
12
|
+
params['maxDepth'] = options.maxDepth;
|
|
13
|
+
}
|
|
14
|
+
const raw = await client.call('screen.snapshot', params);
|
|
15
|
+
const parsed = snapshotSchema.safeParse(raw);
|
|
16
|
+
if (!parsed.success) {
|
|
17
|
+
throw new CliError(`The mod sent a snapshot this CLI cannot read: ${parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('; ')}`, 1, 'This usually means the CLI and the mod are from different releases. Update whichever is older.');
|
|
18
|
+
}
|
|
19
|
+
return parsed.data;
|
|
20
|
+
}
|
|
21
|
+
export async function runSnapshot(global, options) {
|
|
22
|
+
await withClient(global, async ({ client }) => {
|
|
23
|
+
const snapshot = await fetchSnapshot(client, {
|
|
24
|
+
includeHidden: options.includeHidden,
|
|
25
|
+
maxDepth: options.maxDepth === undefined ? undefined : Number(options.maxDepth),
|
|
26
|
+
});
|
|
27
|
+
if (global.json) {
|
|
28
|
+
printJson(snapshot);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
line(formatOutline(snapshot, { includeHidden: options.includeHidden }));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function runFind(global, text, options) {
|
|
35
|
+
await withClient(global, async ({ client }) => {
|
|
36
|
+
const snapshot = await fetchSnapshot(client);
|
|
37
|
+
const matches = findNodes(snapshot, text, options.type);
|
|
38
|
+
if (global.json) {
|
|
39
|
+
printJson(matches.map((match) => ({ path: match.node.path, type: match.node.type, centre: match.centre })));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (matches.length === 0) {
|
|
43
|
+
line(`No widget matches '${text}'${options.type === undefined ? '' : ` of type '${options.type}'`}.`);
|
|
44
|
+
line('Run `clientdevbridge snapshot` to see what is on screen.');
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const match of matches) {
|
|
49
|
+
const at = match.centre === null ? 'no bounds' : `click at ${match.centre.x},${match.centre.y}`;
|
|
50
|
+
line(`${simpleType(match.node)} "${label(match.node)}" ${at} ${match.node.path}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Turns a `--widget` argument into a point to click.
|
|
56
|
+
*
|
|
57
|
+
* Ambiguity is an error rather than a guess: silently clicking the wrong one of two similarly
|
|
58
|
+
* labelled buttons is far worse than being told to be more specific.
|
|
59
|
+
*/
|
|
60
|
+
export async function resolveWidget(client, query) {
|
|
61
|
+
const snapshot = await fetchSnapshot(client);
|
|
62
|
+
const matches = findNodes(snapshot, query);
|
|
63
|
+
if (matches.length === 0) {
|
|
64
|
+
throw new CliError(`No widget matches '${query}' on ${snapshot.screenClass ?? 'this screen'}.`, 1, 'Run `clientdevbridge snapshot` to see the widgets, then pass a label or a /root/children[N] path.');
|
|
65
|
+
}
|
|
66
|
+
if (matches.length > 1) {
|
|
67
|
+
const listed = matches
|
|
68
|
+
.slice(0, 8)
|
|
69
|
+
.map((match) => ` ${simpleType(match.node)} "${label(match.node)}" ${match.node.path}`)
|
|
70
|
+
.join('\n');
|
|
71
|
+
throw new CliError(`'${query}' matches ${matches.length} widgets:\n${listed}`, 1, 'Pass the exact path instead, e.g. --widget /root/children[3]');
|
|
72
|
+
}
|
|
73
|
+
const match = matches[0];
|
|
74
|
+
const point = centre(match.node);
|
|
75
|
+
if (point === null) {
|
|
76
|
+
throw new CliError(`The widget '${query}' (${match.node.path}) reports no bounds, so there is nowhere to click.`, 1, 'Use --at x,y with coordinates read off a screenshot.');
|
|
77
|
+
}
|
|
78
|
+
return point;
|
|
79
|
+
}
|
|
80
|
+
export async function runTooltip(global, options) {
|
|
81
|
+
await withClient(global, async ({ client }) => {
|
|
82
|
+
let point;
|
|
83
|
+
if (options.widget !== undefined) {
|
|
84
|
+
point = await resolveWidget(client, options.widget);
|
|
85
|
+
}
|
|
86
|
+
else if (options.at !== undefined) {
|
|
87
|
+
const { parsePoint } = await import('./input.js');
|
|
88
|
+
point = parsePoint(options.at, '--at');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw new CliError('tooltip needs either --at x,y or --widget <text-or-path>.', 1);
|
|
92
|
+
}
|
|
93
|
+
const result = await client.call('screen.tooltip', point);
|
|
94
|
+
if (global.json) {
|
|
95
|
+
printJson(result);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (result.lines.length === 0) {
|
|
99
|
+
line(`No tooltip at ${point.x},${point.y} (source: ${result.source}).`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
for (const entry of result.lines) {
|
|
103
|
+
line(entry);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The composite an agent reaches for most: open a block's GUI, wait for it, and produce both the
|
|
109
|
+
* outline and a screenshot path in one round trip.
|
|
110
|
+
*/
|
|
111
|
+
export async function runInspectGui(global, x, y, z, options) {
|
|
112
|
+
await withClient(global, async ({ client, paths }) => {
|
|
113
|
+
const opened = await client.call('screen.open', { blockPos: [Number(x), Number(y), Number(z)], approach: options.approach }, 60_000);
|
|
114
|
+
if (opened['opened'] !== true) {
|
|
115
|
+
line(`No screen opened for the block at ${x},${y},${z}. ${opened['hint'] ?? ''}`.trim());
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// One tick so the freshly opened screen has laid itself out before it is described.
|
|
120
|
+
await client.call('wait.ticks', { ticks: 2 });
|
|
121
|
+
const snapshot = await fetchSnapshot(client);
|
|
122
|
+
const shot = await client.call('screenshot', {});
|
|
123
|
+
const file = writeBase64(paths.screenshots, options.name ?? timestampName('inspect-gui'), String(shot['png']));
|
|
124
|
+
if (global.json) {
|
|
125
|
+
printJson({ snapshot, screenshot: file });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
line(formatOutline(snapshot));
|
|
129
|
+
line('');
|
|
130
|
+
printPath(file);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/commands/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type GlobalOptions } from './context.js';
|
|
2
|
+
export interface WorldResetOptions {
|
|
3
|
+
readonly name?: string | undefined;
|
|
4
|
+
readonly template?: string | undefined;
|
|
5
|
+
readonly setup?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function runWorldReset(global: GlobalOptions, options: WorldResetOptions): Promise<void>;
|
|
8
|
+
export declare function runWorldLoad(global: GlobalOptions, name: string): Promise<void>;
|
|
9
|
+
export declare function runWorldLeave(global: GlobalOptions): Promise<void>;
|
|
10
|
+
export declare function runWorldList(global: GlobalOptions): Promise<void>;
|
|
11
|
+
export declare function runCommand(global: GlobalOptions, command: string): Promise<void>;
|
|
12
|
+
export declare function runBlock(global: GlobalOptions, x: string, y: string, z: string, options: {
|
|
13
|
+
nbt: boolean;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function runSetblock(global: GlobalOptions, x: string, y: string, z: string, block: string): Promise<void>;
|
|
16
|
+
export declare function runGive(global: GlobalOptions, item: string, count: string): Promise<void>;
|
|
17
|
+
export declare function runTeleport(global: GlobalOptions, x: string, y: string, z: string, options: {
|
|
18
|
+
yaw?: string | undefined;
|
|
19
|
+
pitch?: string | undefined;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export declare function runLook(global: GlobalOptions, options: {
|
|
22
|
+
at?: string | undefined;
|
|
23
|
+
yaw?: string | undefined;
|
|
24
|
+
pitch?: string | undefined;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
export declare function runInventory(global: GlobalOptions): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=world.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/commands/world.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,cAAc,CAAC;AAG9D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpG;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrF;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxE;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAevE;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBtF;AAUD,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,aAAa,EACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAChE,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACzF,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvE"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { line, printJson } from '../output.js';
|
|
2
|
+
import { withClient } from './context.js';
|
|
3
|
+
import { CliError, EXIT_PROTOCOL } from '../errors.js';
|
|
4
|
+
export async function runWorldReset(global, options) {
|
|
5
|
+
await withClient(global, async ({ client }) => {
|
|
6
|
+
const params = {};
|
|
7
|
+
if (options.name !== undefined) {
|
|
8
|
+
params['name'] = options.name;
|
|
9
|
+
}
|
|
10
|
+
if (options.template !== undefined) {
|
|
11
|
+
params['template'] = options.template;
|
|
12
|
+
}
|
|
13
|
+
if (options.setup !== undefined) {
|
|
14
|
+
params['setup'] = options.setup;
|
|
15
|
+
}
|
|
16
|
+
// Generating and joining a world under software rendering is slow; give it its own budget.
|
|
17
|
+
const result = await client.call('world.reset', params, 180_000);
|
|
18
|
+
if (global.json) {
|
|
19
|
+
printJson(result);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!global.quiet) {
|
|
23
|
+
const spawn = result['spawn'];
|
|
24
|
+
line(`World '${result['world']}' is ready` +
|
|
25
|
+
`${result['template'] === null ? ' (fresh creative superflat)' : ` (from template ${result['template']})`}` +
|
|
26
|
+
`, player at ${spawn.join(', ')}.`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export async function runWorldLoad(global, name) {
|
|
31
|
+
await withClient(global, async ({ client }) => {
|
|
32
|
+
const result = await client.call('world.load', { name }, 180_000);
|
|
33
|
+
if (global.json) {
|
|
34
|
+
printJson(result);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!global.quiet) {
|
|
38
|
+
line(`Loaded world '${result['world']}'.`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export async function runWorldLeave(global) {
|
|
43
|
+
await withClient(global, async ({ client }) => {
|
|
44
|
+
const result = await client.call('world.leave', {}, 60_000);
|
|
45
|
+
if (global.json) {
|
|
46
|
+
printJson(result);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!global.quiet) {
|
|
50
|
+
line('Left the world.');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export async function runWorldList(global) {
|
|
55
|
+
await withClient(global, async ({ client }) => {
|
|
56
|
+
const result = await client.call('world.list');
|
|
57
|
+
if (global.json) {
|
|
58
|
+
printJson(result);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (result.worlds.length === 0) {
|
|
62
|
+
line('No worlds yet. Create one with: clientdevbridge world-reset');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const world of result.worlds) {
|
|
66
|
+
line(world);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export async function runCommand(global, command) {
|
|
71
|
+
await withClient(global, async ({ client }) => {
|
|
72
|
+
const result = await client.call('world.command', { command }, 60_000);
|
|
73
|
+
if (global.json) {
|
|
74
|
+
printJson(result);
|
|
75
|
+
}
|
|
76
|
+
else if (result.output.length === 0) {
|
|
77
|
+
if (!global.quiet) {
|
|
78
|
+
line(result.success ? '(the command produced no output)' : '(the command failed with no output)');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
for (const entry of result.output) {
|
|
83
|
+
line(entry);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// A failing command still prints something, so without this a scripted setup would sail past
|
|
87
|
+
// a scene that was never built and leave the caller debugging a phantom.
|
|
88
|
+
if (!result.success) {
|
|
89
|
+
process.exitCode = EXIT_PROTOCOL;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function parsePosition(x, y, z) {
|
|
94
|
+
const parsed = { x: Number(x), y: Number(y), z: Number(z) };
|
|
95
|
+
if (Number.isNaN(parsed.x) || Number.isNaN(parsed.y) || Number.isNaN(parsed.z)) {
|
|
96
|
+
throw new CliError(`Expected three numbers for a block position, but got '${x} ${y} ${z}'.`, 1);
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
export async function runBlock(global, x, y, z, options) {
|
|
101
|
+
await withClient(global, async ({ client }) => {
|
|
102
|
+
const position = parsePosition(x, y, z);
|
|
103
|
+
const result = await client.call('world.block', { ...position, nbt: options.nbt });
|
|
104
|
+
if (global.json) {
|
|
105
|
+
printJson(result);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
line(String(result['state']));
|
|
109
|
+
const blockEntity = result['blockEntity'];
|
|
110
|
+
if (blockEntity !== null) {
|
|
111
|
+
line(`block entity: ${blockEntity['type']}`);
|
|
112
|
+
if (blockEntity['nbt'] !== undefined) {
|
|
113
|
+
line(String(blockEntity['nbt']));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export async function runSetblock(global, x, y, z, block) {
|
|
119
|
+
const position = parsePosition(x, y, z);
|
|
120
|
+
await runCommand(global, `setblock ${position.x} ${position.y} ${position.z} ${block}`);
|
|
121
|
+
}
|
|
122
|
+
export async function runGive(global, item, count) {
|
|
123
|
+
await runCommand(global, `give @s ${item} ${count}`);
|
|
124
|
+
}
|
|
125
|
+
export async function runTeleport(global, x, y, z, options) {
|
|
126
|
+
await withClient(global, async ({ client }) => {
|
|
127
|
+
const position = parsePosition(x, y, z);
|
|
128
|
+
const params = { ...position };
|
|
129
|
+
if (options.yaw !== undefined) {
|
|
130
|
+
params['yaw'] = Number(options.yaw);
|
|
131
|
+
}
|
|
132
|
+
if (options.pitch !== undefined) {
|
|
133
|
+
params['pitch'] = Number(options.pitch);
|
|
134
|
+
}
|
|
135
|
+
const result = await client.call('player.teleport', params);
|
|
136
|
+
if (global.json) {
|
|
137
|
+
printJson(result);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!global.quiet) {
|
|
141
|
+
const pos = result['pos'];
|
|
142
|
+
const requested = result['requested'];
|
|
143
|
+
const at = pos.map((value) => value.toFixed(2)).join(', ');
|
|
144
|
+
line(`Player at ${at}, facing ${result['yaw']}/${result['pitch']}.`);
|
|
145
|
+
// Gravity acts between the teleport and the reply, so the reported y can be lower than the
|
|
146
|
+
// one asked for. Saying so is the difference between a confusing number and an expected one.
|
|
147
|
+
if (requested !== undefined && requested.some((value, index) => Math.abs(value - (pos[index] ?? 0)) > 0.01)) {
|
|
148
|
+
line(`(asked for ${requested.map((value) => value.toFixed(2)).join(', ')}; the player has since fallen or been pushed)`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export async function runLook(global, options) {
|
|
154
|
+
await withClient(global, async ({ client }) => {
|
|
155
|
+
const params = {};
|
|
156
|
+
if (options.at !== undefined) {
|
|
157
|
+
const parts = options.at.split(',').map((part) => Number(part.trim()));
|
|
158
|
+
if (parts.length !== 3 || parts.some((value) => Number.isNaN(value))) {
|
|
159
|
+
throw new CliError(`--at must be 'x,y,z', but was '${options.at}'.`, 1);
|
|
160
|
+
}
|
|
161
|
+
params['at'] = parts;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
if (options.yaw !== undefined) {
|
|
165
|
+
params['yaw'] = Number(options.yaw);
|
|
166
|
+
}
|
|
167
|
+
if (options.pitch !== undefined) {
|
|
168
|
+
params['pitch'] = Number(options.pitch);
|
|
169
|
+
}
|
|
170
|
+
if (Object.keys(params).length === 0) {
|
|
171
|
+
throw new CliError('look needs --at x,y,z, or --yaw and/or --pitch.', 1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const result = await client.call('player.look', params);
|
|
175
|
+
if (global.json) {
|
|
176
|
+
printJson(result);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!global.quiet) {
|
|
180
|
+
line(`Facing ${Number(result['yaw']).toFixed(1)}/${Number(result['pitch']).toFixed(1)}.`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export async function runInventory(global) {
|
|
185
|
+
await withClient(global, async ({ client }) => {
|
|
186
|
+
const result = await client.call('player.inventory');
|
|
187
|
+
if (global.json) {
|
|
188
|
+
printJson(result);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const slot of result.slots) {
|
|
192
|
+
if (slot.item === null) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const marker = slot.index === result.selected ? '>' : ' ';
|
|
196
|
+
line(`${marker} [${String(slot.index).padStart(2)}] ${slot.item} x${slot.count}`);
|
|
197
|
+
}
|
|
198
|
+
if (!global.quiet) {
|
|
199
|
+
line(`# selected hotbar slot ${result.selected}`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=world.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world.js","sourceRoot":"","sources":["../../src/commands/world.ts"],"names":[],"mappings":"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"}
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type Loader = 'fabric' | 'neoforge';
|
|
2
|
+
export interface ProjectInfo {
|
|
3
|
+
readonly projectDir: string;
|
|
4
|
+
readonly minecraftVersion: string;
|
|
5
|
+
readonly loader: Loader;
|
|
6
|
+
/** The Gradle task that launches the client, e.g. `:loader-neoforge:runClient`. */
|
|
7
|
+
readonly gradleTask: string;
|
|
8
|
+
/** Where that task's working directory is, used to pin `options.txt`. */
|
|
9
|
+
readonly runDir: string;
|
|
10
|
+
readonly gradleWrapper: string;
|
|
11
|
+
/**
|
|
12
|
+
* The Java version the project declares. The loader plugins check the JDK Gradle itself runs on,
|
|
13
|
+
* so this is not a toolchain hint that Gradle can satisfy on its own.
|
|
14
|
+
*/
|
|
15
|
+
readonly javaVersion: number;
|
|
16
|
+
/** How each value was arrived at, so `doctor` can explain itself. */
|
|
17
|
+
readonly detected: {
|
|
18
|
+
minecraftVersion: string;
|
|
19
|
+
loader: string;
|
|
20
|
+
gradleTask: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function readGradleProperties(projectDir: string): Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Detects which loaders a checkout can run, by looking for the Gradle plugin each one applies.
|
|
26
|
+
* ModDevGradle / NeoGradle mean NeoForge; Loom means Fabric.
|
|
27
|
+
*/
|
|
28
|
+
export declare function detectLoaders(projectDir: string): Loader[];
|
|
29
|
+
/**
|
|
30
|
+
* Picks the Gradle task that runs the client. Multiloader checkouts (the Cyclops layout) expose it
|
|
31
|
+
* per loader module; a single-module mod exposes a plain `runClient`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectGradleTask(projectDir: string, loader: Loader): {
|
|
34
|
+
task: string;
|
|
35
|
+
runDir: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Picks the directory the client will use as its game directory.
|
|
39
|
+
*
|
|
40
|
+
* There is no single answer to guess at: the run directory is chosen by the Gradle plugin, and the
|
|
41
|
+
* conventions disagree. NeoGradle and the multiloader Loom convention use `runs/client`;
|
|
42
|
+
* ModDevGradle 2, which is what the Minecraft 26 line builds on, uses `run`. Getting this wrong is
|
|
43
|
+
* not loud — the determinism options are simply pinned into a file the game never reads, and the
|
|
44
|
+
* first hint is a screenshot that does not match its golden.
|
|
45
|
+
*
|
|
46
|
+
* So: believe the evidence on disk when a previous run left some, and fall back to the convention
|
|
47
|
+
* order otherwise. `start` corrects a wrong guess against the game directory the client reports
|
|
48
|
+
* once it is up.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectRunDir(baseDir: string, projectDir?: string, loader?: Loader): string;
|
|
51
|
+
/** Minecraft 1.21 needs Java 21 and Minecraft 26 needs 25; the project states which. */
|
|
52
|
+
export declare function declaredJavaVersion(properties: Record<string, string>): number;
|
|
53
|
+
export interface DetectOverrides {
|
|
54
|
+
readonly minecraftVersion?: string | undefined;
|
|
55
|
+
readonly loader?: Loader | undefined;
|
|
56
|
+
}
|
|
57
|
+
export declare function detectProject(projectDir: string, overrides?: DetectOverrides): ProjectInfo;
|
|
58
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB/E;AAwBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAOrG;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAyB3F;AA2BD,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAG9E;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,eAAoB,GAAG,WAAW,CA2D9F"}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { CliError } from './errors.js';
|
|
4
|
+
export function readGradleProperties(projectDir) {
|
|
5
|
+
const file = path.join(projectDir, 'gradle.properties');
|
|
6
|
+
if (!fs.existsSync(file)) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const properties = {};
|
|
10
|
+
for (const rawLine of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
11
|
+
const line = rawLine.trim();
|
|
12
|
+
if (line.length === 0 || line.startsWith('#') || line.startsWith('!')) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const separator = line.indexOf('=');
|
|
16
|
+
if (separator > 0) {
|
|
17
|
+
properties[line.slice(0, separator).trim()] = line.slice(separator + 1).trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return properties;
|
|
21
|
+
}
|
|
22
|
+
function readIfExists(file) {
|
|
23
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
24
|
+
}
|
|
25
|
+
/** Every Gradle script that could say how this project is configured, as one blob to search. */
|
|
26
|
+
function buildSources(projectDir) {
|
|
27
|
+
return [
|
|
28
|
+
readIfExists(path.join(projectDir, 'build.gradle')),
|
|
29
|
+
readIfExists(path.join(projectDir, 'build.gradle.kts')),
|
|
30
|
+
readIfExists(path.join(projectDir, 'settings.gradle')),
|
|
31
|
+
...['loader-fabric', 'loader-neoforge'].flatMap((module) => [
|
|
32
|
+
readIfExists(path.join(projectDir, module, 'build.gradle')),
|
|
33
|
+
readIfExists(path.join(projectDir, module, 'build.gradle.kts')),
|
|
34
|
+
]),
|
|
35
|
+
...(fs.existsSync(path.join(projectDir, 'buildSrc/src/main/groovy'))
|
|
36
|
+
? fs
|
|
37
|
+
.readdirSync(path.join(projectDir, 'buildSrc/src/main/groovy'))
|
|
38
|
+
.map((entry) => readIfExists(path.join(projectDir, 'buildSrc/src/main/groovy', entry)))
|
|
39
|
+
: []),
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Detects which loaders a checkout can run, by looking for the Gradle plugin each one applies.
|
|
44
|
+
* ModDevGradle / NeoGradle mean NeoForge; Loom means Fabric.
|
|
45
|
+
*/
|
|
46
|
+
export function detectLoaders(projectDir) {
|
|
47
|
+
const sources = buildSources(projectDir);
|
|
48
|
+
const loaders = [];
|
|
49
|
+
if (/fabric-loom/.test(sources) || fs.existsSync(path.join(projectDir, 'loader-fabric'))) {
|
|
50
|
+
loaders.push('fabric');
|
|
51
|
+
}
|
|
52
|
+
if (/net\.neoforged\.(moddev|gradle)/.test(sources) ||
|
|
53
|
+
fs.existsSync(path.join(projectDir, 'loader-neoforge'))) {
|
|
54
|
+
loaders.push('neoforge');
|
|
55
|
+
}
|
|
56
|
+
return loaders;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Picks the Gradle task that runs the client. Multiloader checkouts (the Cyclops layout) expose it
|
|
60
|
+
* per loader module; a single-module mod exposes a plain `runClient`.
|
|
61
|
+
*/
|
|
62
|
+
export function detectGradleTask(projectDir, loader) {
|
|
63
|
+
const moduleName = `loader-${loader}`;
|
|
64
|
+
if (fs.existsSync(path.join(projectDir, moduleName))) {
|
|
65
|
+
const moduleDir = path.join(projectDir, moduleName);
|
|
66
|
+
return { task: `:${moduleName}:runClient`, runDir: detectRunDir(moduleDir, projectDir, loader) };
|
|
67
|
+
}
|
|
68
|
+
return { task: 'runClient', runDir: detectRunDir(projectDir, projectDir, loader) };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Files and directories only the game itself creates. `options.txt` is deliberately not among
|
|
72
|
+
* them: the CLI writes that one, so finding it proves nothing about where the game runs.
|
|
73
|
+
*/
|
|
74
|
+
const RUN_DIR_EVIDENCE = ['logs', 'saves', 'crash-reports', 'usercache.json'];
|
|
75
|
+
/**
|
|
76
|
+
* Picks the directory the client will use as its game directory.
|
|
77
|
+
*
|
|
78
|
+
* There is no single answer to guess at: the run directory is chosen by the Gradle plugin, and the
|
|
79
|
+
* conventions disagree. NeoGradle and the multiloader Loom convention use `runs/client`;
|
|
80
|
+
* ModDevGradle 2, which is what the Minecraft 26 line builds on, uses `run`. Getting this wrong is
|
|
81
|
+
* not loud — the determinism options are simply pinned into a file the game never reads, and the
|
|
82
|
+
* first hint is a screenshot that does not match its golden.
|
|
83
|
+
*
|
|
84
|
+
* So: believe the evidence on disk when a previous run left some, and fall back to the convention
|
|
85
|
+
* order otherwise. `start` corrects a wrong guess against the game directory the client reports
|
|
86
|
+
* once it is up.
|
|
87
|
+
*/
|
|
88
|
+
export function detectRunDir(baseDir, projectDir = baseDir, loader) {
|
|
89
|
+
const candidates = [path.join(baseDir, 'runs', 'client'), path.join(baseDir, 'run')];
|
|
90
|
+
let best = null;
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
for (const marker of RUN_DIR_EVIDENCE) {
|
|
93
|
+
let usedAt;
|
|
94
|
+
try {
|
|
95
|
+
usedAt = fs.statSync(path.join(candidate, marker)).mtimeMs;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// A checkout that switched Gradle plugins has both; the one used most recently is the live one.
|
|
101
|
+
if (best === null || usedAt > best.usedAt) {
|
|
102
|
+
best = { dir: candidate, usedAt };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (best !== null) {
|
|
107
|
+
return best.dir;
|
|
108
|
+
}
|
|
109
|
+
// Nothing has run yet, so ask the build what it is going to do. Getting this right on a cold
|
|
110
|
+
// checkout is what keeps the very first launch deterministic -- and keeps `start` from opening
|
|
111
|
+
// with a correction notice about a directory the caller never mentioned.
|
|
112
|
+
const declared = declaredRunDir(buildSources(projectDir), loader);
|
|
113
|
+
return declared === null ? candidates[0] : path.join(baseDir, ...declared.split('/'));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The run directory the build declares, as a path relative to the module.
|
|
117
|
+
*
|
|
118
|
+
* Loom's multiloader convention states it outright. The NeoForge plugins do not, and the two
|
|
119
|
+
* generations disagree: NeoGradle runs in `runs/<name>`, ModDevGradle 2 -- which the Minecraft 26
|
|
120
|
+
* line builds on -- in `run`.
|
|
121
|
+
*
|
|
122
|
+
* @return the declared directory, or null when the build says nothing useful
|
|
123
|
+
*/
|
|
124
|
+
function declaredRunDir(sources, loader) {
|
|
125
|
+
if (loader === 'fabric') {
|
|
126
|
+
const explicit = /\brunDir\s*[( ]\s*['"]([^'"]+)['"]/.exec(sources);
|
|
127
|
+
return explicit === null ? null : explicit[1];
|
|
128
|
+
}
|
|
129
|
+
if (loader === 'neoforge') {
|
|
130
|
+
if (/net\.neoforged\.moddev/.test(sources)) {
|
|
131
|
+
return 'run';
|
|
132
|
+
}
|
|
133
|
+
if (/net\.neoforged\.gradle/.test(sources)) {
|
|
134
|
+
return 'runs/client';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/** Minecraft 1.21 needs Java 21 and Minecraft 26 needs 25; the project states which. */
|
|
140
|
+
export function declaredJavaVersion(properties) {
|
|
141
|
+
const declared = Number(properties['java_version'] ?? '21');
|
|
142
|
+
return Number.isNaN(declared) ? 21 : declared;
|
|
143
|
+
}
|
|
144
|
+
export function detectProject(projectDir, overrides = {}) {
|
|
145
|
+
const resolved = path.resolve(projectDir);
|
|
146
|
+
const wrapper = path.join(resolved, process.platform === 'win32' ? 'gradlew.bat' : 'gradlew');
|
|
147
|
+
if (!fs.existsSync(wrapper)) {
|
|
148
|
+
throw new CliError(`${resolved} does not look like a Gradle mod project: no gradlew wrapper found.`, 2, 'Pass --project <dir> pointing at the mod checkout you want to launch.');
|
|
149
|
+
}
|
|
150
|
+
const properties = readGradleProperties(resolved);
|
|
151
|
+
const minecraftVersion = overrides.minecraftVersion ?? properties['minecraft_version'];
|
|
152
|
+
if (minecraftVersion === undefined || minecraftVersion.length === 0) {
|
|
153
|
+
throw new CliError(`Could not determine the Minecraft version of ${resolved}: no 'minecraft_version' in gradle.properties.`, 2, 'Pass --mc-version <version> explicitly.');
|
|
154
|
+
}
|
|
155
|
+
const available = detectLoaders(resolved);
|
|
156
|
+
let loader = overrides.loader;
|
|
157
|
+
if (loader === undefined) {
|
|
158
|
+
if (available.length === 0) {
|
|
159
|
+
throw new CliError(`Could not determine the mod loader of ${resolved}: found neither Loom (Fabric) nor ModDevGradle/NeoGradle (NeoForge).`, 2, 'Pass --loader fabric|neoforge explicitly.');
|
|
160
|
+
}
|
|
161
|
+
// NeoForge first when both are present: it is the primary loader in the Cyclops layout.
|
|
162
|
+
loader = available.includes('neoforge') ? 'neoforge' : available[0];
|
|
163
|
+
}
|
|
164
|
+
else if (available.length > 0 && !available.includes(loader)) {
|
|
165
|
+
throw new CliError(`${resolved} does not appear to support the ${loader} loader (detected: ${available.join(', ') || 'none'}).`, 2, 'Drop --loader to use the detected one.');
|
|
166
|
+
}
|
|
167
|
+
const { task, runDir } = detectGradleTask(resolved, loader);
|
|
168
|
+
return {
|
|
169
|
+
projectDir: resolved,
|
|
170
|
+
minecraftVersion,
|
|
171
|
+
loader,
|
|
172
|
+
gradleTask: task,
|
|
173
|
+
runDir,
|
|
174
|
+
javaVersion: declaredJavaVersion(properties),
|
|
175
|
+
gradleWrapper: wrapper,
|
|
176
|
+
detected: {
|
|
177
|
+
minecraftVersion: overrides.minecraftVersion === undefined ? "gradle.properties 'minecraft_version'" : '--mc-version',
|
|
178
|
+
loader: overrides.loader === undefined ? `applied Gradle plugins (${available.join(', ')})` : '--loader',
|
|
179
|
+
gradleTask: fs.existsSync(path.join(resolved, `loader-${loader}`))
|
|
180
|
+
? 'multiloader layout'
|
|
181
|
+
: 'single-module layout',
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=detect.js.map
|