kustom-mc 0.1.3 → 0.1.4
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/dist/commands/init.js +3 -11
- package/dist/commands/login.js +27 -1
- package/dist/commands/new.js +29 -23
- package/dist/commands/validate.js +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +25 -1
- package/dist/runtime.d.ts +21 -3
- package/dist/runtime.js +2 -27
- package/dist/types/__tests__/debug-inference.d.ts +4 -0
- package/dist/types/__tests__/debug-inference.js +25 -0
- package/dist/types/__tests__/defineScript-methods-dx-test.d.ts +27 -0
- package/dist/types/__tests__/defineScript-methods-dx-test.js +76 -0
- package/dist/types/__tests__/defineScript-methods-negative-test.d.ts +16 -0
- package/dist/types/__tests__/defineScript-methods-negative-test.js +36 -0
- package/dist/types/__tests__/methods-type-test.d.ts +10 -0
- package/dist/types/__tests__/methods-type-test.js +39 -0
- package/dist/types/index.d.ts +91 -3
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -26,11 +26,9 @@ export const initCommand = new Command('init')
|
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
// Create directory structure
|
|
29
|
+
// Create directory structure (all scripts go in scripts/, type determined by __type)
|
|
30
30
|
const dirs = [
|
|
31
31
|
'scripts/lib',
|
|
32
|
-
'blocks',
|
|
33
|
-
'items',
|
|
34
32
|
'textures',
|
|
35
33
|
'gui',
|
|
36
34
|
'models',
|
|
@@ -68,8 +66,6 @@ export const initCommand = new Command('init')
|
|
|
68
66
|
},
|
|
69
67
|
include: [
|
|
70
68
|
"scripts/**/*.ts",
|
|
71
|
-
"blocks/**/*.ts",
|
|
72
|
-
"items/**/*.ts",
|
|
73
69
|
"dist/types/**/*.d.ts",
|
|
74
70
|
".kustom/types/**/*.d.ts",
|
|
75
71
|
"node_modules/kustom-mc/dist/types/globals.d.ts"
|
|
@@ -80,9 +76,7 @@ export const initCommand = new Command('init')
|
|
|
80
76
|
// Write kustom.config.json with manifest
|
|
81
77
|
const kustomConfig = {
|
|
82
78
|
include: [
|
|
83
|
-
"scripts/**/*.ts"
|
|
84
|
-
"blocks/**/*.ts",
|
|
85
|
-
"items/**/*.ts"
|
|
79
|
+
"scripts/**/*.ts"
|
|
86
80
|
],
|
|
87
81
|
exclude: [
|
|
88
82
|
"**/*.test.ts",
|
|
@@ -189,11 +183,9 @@ dist/
|
|
|
189
183
|
├── kustom.config.json # Pack configuration & manifest
|
|
190
184
|
├── tsconfig.json # TypeScript configuration
|
|
191
185
|
├── package.json # npm configuration
|
|
192
|
-
|
|
186
|
+
├── scripts/ # All scripts (type determined by defineScript/defineBlock/defineItem)
|
|
193
187
|
│ ├── example.ts # Example script
|
|
194
188
|
│ └── lib/ # Shared utilities
|
|
195
|
-
├── blocks/ # Block definitions
|
|
196
|
-
├── items/ # Item definitions
|
|
197
189
|
├── textures/ # Texture files
|
|
198
190
|
├── models/ # Model files
|
|
199
191
|
├── sounds/ # Sound files
|
package/dist/commands/login.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import * as readline from 'readline';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
|
-
import { loadConfig } from '../config.js';
|
|
4
|
+
import { loadConfig, saveConfigServerUrl } from '../config.js';
|
|
4
5
|
import { saveServerToken, getServerCredential, normalizeServerUrl, listStoredServers, removeServerCredential } from '../credentials.js';
|
|
5
6
|
/**
|
|
6
7
|
* Validate a token with the server.
|
|
@@ -60,6 +61,7 @@ export const loginCommand = new Command('login')
|
|
|
60
61
|
// Determine server URL
|
|
61
62
|
let serverUrl;
|
|
62
63
|
let token;
|
|
64
|
+
let serverUrlExplicit = false; // true when user provided the URL as a CLI argument
|
|
63
65
|
// If only one argument provided, it might be the token (use config for server)
|
|
64
66
|
if (serverUrlArg && !tokenArg && !serverUrlArg.includes('://') && !serverUrlArg.includes(':')) {
|
|
65
67
|
// Looks like a token, not a URL
|
|
@@ -76,6 +78,7 @@ export const loginCommand = new Command('login')
|
|
|
76
78
|
else if (serverUrlArg) {
|
|
77
79
|
serverUrl = serverUrlArg;
|
|
78
80
|
token = tokenArg;
|
|
81
|
+
serverUrlExplicit = true;
|
|
79
82
|
}
|
|
80
83
|
else {
|
|
81
84
|
// No arguments - use config
|
|
@@ -159,6 +162,29 @@ export const loginCommand = new Command('login')
|
|
|
159
162
|
console.log(` Token expires: ${result.expiresAt}`);
|
|
160
163
|
}
|
|
161
164
|
console.log(`\nCredentials saved to ~/.kustom/credentials.json`);
|
|
165
|
+
// Offer to update kustom.config.json if the user explicitly provided a server URL
|
|
166
|
+
if (serverUrlExplicit) {
|
|
167
|
+
const config = loadConfig(process.cwd());
|
|
168
|
+
const configServerUrl = config.server?.url ? normalizeServerUrl(config.server.url) : undefined;
|
|
169
|
+
if (configServerUrl !== serverUrl) {
|
|
170
|
+
const rl = readline.createInterface({
|
|
171
|
+
input: process.stdin,
|
|
172
|
+
output: process.stdout
|
|
173
|
+
});
|
|
174
|
+
const answer = await new Promise((resolve) => {
|
|
175
|
+
rl.question(chalk.yellow(`Update server.url in kustom.config.json to ${serverUrl}? [y/N] `), resolve);
|
|
176
|
+
});
|
|
177
|
+
rl.close();
|
|
178
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
179
|
+
if (saveConfigServerUrl(process.cwd(), serverUrl)) {
|
|
180
|
+
console.log(chalk.green('Updated server.url in kustom.config.json'));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log(chalk.yellow('Could not update kustom.config.json (file not found or not writable)'));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
162
188
|
}
|
|
163
189
|
catch (error) {
|
|
164
190
|
console.error(chalk.red(`Login failed: ${error instanceof Error ? error.message : error}`));
|
package/dist/commands/new.js
CHANGED
|
@@ -22,32 +22,37 @@ export default defineScript({
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
`,
|
|
25
|
-
block: `import {
|
|
25
|
+
block: `import { defineBlock } from 'kustom-mc';
|
|
26
26
|
|
|
27
|
-
export default
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
export default defineBlock({
|
|
28
|
+
id: "{{name}}",
|
|
29
|
+
model: "kustom:block/{{name}}",
|
|
30
|
+
collision: "full",
|
|
31
|
+
autoPlace: true,
|
|
32
|
+
|
|
33
|
+
onClick(event) {
|
|
34
|
+
event.player.sendMessage("You clicked {{name}}!");
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
onBreak(event) {
|
|
38
|
+
event.player.sendMessage("{{name}} broken!");
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
onPlace(event) {
|
|
42
|
+
event.player.sendMessage("{{name}} placed!");
|
|
37
43
|
}
|
|
38
44
|
});
|
|
39
45
|
`,
|
|
40
|
-
item: `import {
|
|
46
|
+
item: `import { defineItem } from 'kustom-mc';
|
|
41
47
|
|
|
42
|
-
export default
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.register();
|
|
48
|
+
export default defineItem({
|
|
49
|
+
id: "{{name}}",
|
|
50
|
+
material: "PAPER",
|
|
51
|
+
model: "kustom:item/{{name}}",
|
|
52
|
+
displayName: "{{name}}",
|
|
53
|
+
|
|
54
|
+
onRightClick(event) {
|
|
55
|
+
event.player.sendMessage("You used {{name}}!");
|
|
51
56
|
}
|
|
52
57
|
});
|
|
53
58
|
`,
|
|
@@ -94,10 +99,11 @@ export const newCommand = new Command('new')
|
|
|
94
99
|
process.exit(1);
|
|
95
100
|
}
|
|
96
101
|
// Determine output directory and file
|
|
102
|
+
// All script types go in scripts/ — type is determined by defineScript/defineBlock/defineItem
|
|
97
103
|
const dirMap = {
|
|
98
104
|
script: 'scripts',
|
|
99
|
-
block: '
|
|
100
|
-
item: '
|
|
105
|
+
block: 'scripts',
|
|
106
|
+
item: 'scripts',
|
|
101
107
|
gui: 'scripts'
|
|
102
108
|
};
|
|
103
109
|
const dir = dirMap[type];
|
|
@@ -30,7 +30,7 @@ export const validateCommand = new Command('validate')
|
|
|
30
30
|
}
|
|
31
31
|
console.log('Validating scripts...');
|
|
32
32
|
// Find all TypeScript files
|
|
33
|
-
const patterns = config.include || ['scripts/**/*.ts'
|
|
33
|
+
const patterns = config.include || ['scripts/**/*.ts'];
|
|
34
34
|
const excludePatterns = config.exclude || [];
|
|
35
35
|
const files = [];
|
|
36
36
|
for (const pattern of patterns) {
|
package/dist/config.d.ts
CHANGED
|
@@ -80,3 +80,9 @@ export declare function parseCrossPackImport(importPath: string): {
|
|
|
80
80
|
* Create a fully qualified namespaced ID.
|
|
81
81
|
*/
|
|
82
82
|
export declare function createNamespacedId(packId: string, type: string, name: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Update the server.url in kustom.config.json.
|
|
85
|
+
* Preserves all other config properties.
|
|
86
|
+
* Returns true if the file was updated, false if no config file exists.
|
|
87
|
+
*/
|
|
88
|
+
export declare function saveConfigServerUrl(cwd: string, serverUrl: string): boolean;
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
const defaultConfig = {
|
|
4
|
-
include: ['scripts/**/*.ts'
|
|
4
|
+
include: ['scripts/**/*.ts'],
|
|
5
5
|
exclude: ['**/*.test.ts', '**/*.spec.ts'],
|
|
6
6
|
outDir: 'dist',
|
|
7
7
|
lib: ['scripts/lib'],
|
|
@@ -159,3 +159,27 @@ export function parseCrossPackImport(importPath) {
|
|
|
159
159
|
export function createNamespacedId(packId, type, name) {
|
|
160
160
|
return `${packId}:${type}/${name}`;
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Update the server.url in kustom.config.json.
|
|
164
|
+
* Preserves all other config properties.
|
|
165
|
+
* Returns true if the file was updated, false if no config file exists.
|
|
166
|
+
*/
|
|
167
|
+
export function saveConfigServerUrl(cwd, serverUrl) {
|
|
168
|
+
const configPath = path.join(cwd, 'kustom.config.json');
|
|
169
|
+
if (!fs.existsSync(configPath)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
174
|
+
const config = JSON.parse(content);
|
|
175
|
+
if (!config.server || typeof config.server !== 'object') {
|
|
176
|
+
config.server = {};
|
|
177
|
+
}
|
|
178
|
+
config.server.url = serverUrl;
|
|
179
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* plain objects with __type property injected.
|
|
9
9
|
*/
|
|
10
10
|
import './types/globals.js';
|
|
11
|
-
import type { ScriptConfig, ExportedScript, PropSchema, EventSchema, EmptyEventSchema, BlockConfig, BlockDefinition, ItemConfig, ItemDefinition, ScreenConstructor, ContainerConstructor, ElementFactory, ScriptableLocationFactory, PropsAPI, CellStateUtils } from './types/index.js';
|
|
11
|
+
import type { ScriptConfig, ScriptConfigWithMethods, ExportedScript, PropSchema, EventSchema, EmptyEventSchema, BlockConfig, BlockDefinition, ItemConfig, ItemDefinition, ScreenConstructor, ContainerConstructor, ElementFactory, ScriptableLocationFactory, PropsAPI, CellStateUtils, MethodMap } from './types/index.js';
|
|
12
12
|
/**
|
|
13
13
|
* Screen constructor - create new screens for GUIs.
|
|
14
14
|
* Actual value is injected by Java at runtime.
|
|
@@ -59,13 +59,31 @@ export declare const ScriptableLocation: ScriptableLocationFactory;
|
|
|
59
59
|
* onSuccess: Props.Boolean(),
|
|
60
60
|
* onError: Props.String(),
|
|
61
61
|
* },
|
|
62
|
-
*
|
|
62
|
+
* methods: {
|
|
63
|
+
* greet({ executor, props }) {
|
|
64
|
+
* executor.asPlayer()?.sendMessage(props.message);
|
|
65
|
+
* },
|
|
66
|
+
* },
|
|
67
|
+
* run({ process, props, methods }) {
|
|
63
68
|
* const player = process.getPlayer(props.player);
|
|
64
69
|
* player.sendMessage(props.message);
|
|
70
|
+
* methods.greet(); // ctx auto-injected!
|
|
65
71
|
* process.emit("onSuccess", true); // Typed!
|
|
66
72
|
* }
|
|
67
73
|
* });
|
|
68
74
|
*/
|
|
75
|
+
/**
|
|
76
|
+
* Define a kustom script with type safety and strongly-typed methods.
|
|
77
|
+
*
|
|
78
|
+
* Overload 1: Script WITH methods — M is inferred from the methods object,
|
|
79
|
+
* providing full autocomplete and type safety for ctx.methods.
|
|
80
|
+
*/
|
|
81
|
+
export declare function defineScript<P extends PropSchema, E extends EventSchema, M extends MethodMap<P, E>>(config: ScriptConfigWithMethods<P, E, M>): ExportedScript<P, E>;
|
|
82
|
+
/**
|
|
83
|
+
* Define a kustom script with type safety.
|
|
84
|
+
*
|
|
85
|
+
* Overload 2: Script WITHOUT methods — simpler signature, backward compatible.
|
|
86
|
+
*/
|
|
69
87
|
export declare function defineScript<P extends PropSchema = Record<string, never>, E extends EventSchema = EmptyEventSchema>(config: ScriptConfig<P, E>): ExportedScript<P, E>;
|
|
70
88
|
/**
|
|
71
89
|
* Define a custom block with type safety.
|
|
@@ -113,4 +131,4 @@ export declare function defineBlock(config: BlockConfig): BlockDefinition;
|
|
|
113
131
|
* });
|
|
114
132
|
*/
|
|
115
133
|
export declare function defineItem(config: ItemConfig): ItemDefinition;
|
|
116
|
-
export type { ScriptConfig, ScriptDefinition, ExportedScript, ScriptContext, PropSchema, PropAPI, InferProps, PropsAPI, EventSchema, EmptyEventSchema, InferEventPayload, ScriptModule, ScriptProcess, TypedScriptProcess, InferScriptModule, McModuleAPI, TypedMcModuleAPI, ProcessExecutorAPI, CameraAPI, SessionAPI, ChatGUIAPI, CommandManager, BetterModelEntityAPI, ShaperAPI, ItemAPI, DefinitionBuilder, ScriptablePlayer, BlockConfig, BlockDefinition, BlockClickEvent, BlockBreakEvent, BlockPlaceEvent, ItemConfig, ItemDefinition, ScreenConstructor, ContainerConstructor, ElementFactory, ScriptableLocationFactory, CellStateUtils, } from './types/index.js';
|
|
134
|
+
export type { ScriptConfig, ScriptDefinition, ExportedScript, BaseScriptContext, ScriptContext, PropSchema, PropAPI, InferProps, PropsAPI, WrappedMethods, MethodMap, EventSchema, EmptyEventSchema, InferEventPayload, ScriptModule, ScriptProcess, TypedScriptProcess, InferScriptModule, McModuleAPI, TypedMcModuleAPI, ProcessExecutorAPI, CameraAPI, SessionAPI, ChatGUIAPI, CommandManager, BetterModelEntityAPI, ShaperAPI, ItemAPI, DefinitionBuilder, ScriptablePlayer, BlockConfig, BlockDefinition, BlockClickEvent, BlockBreakEvent, BlockPlaceEvent, ItemConfig, ItemDefinition, ScreenConstructor, ContainerConstructor, ElementFactory, ScriptableLocationFactory, CellStateUtils, } from './types/index.js';
|
package/dist/runtime.js
CHANGED
|
@@ -9,36 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
// Import globals for side effects - makes console, setTimeout, etc. available
|
|
11
11
|
import './types/globals.js';
|
|
12
|
-
|
|
13
|
-
* Define a kustom script with type safety.
|
|
14
|
-
*
|
|
15
|
-
* This is a type-only helper that returns its input unchanged.
|
|
16
|
-
* The Java ProcessManager extracts the `run` function and calls it
|
|
17
|
-
* with the ScriptContext.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* import { defineScript, Props } from 'kustom-mc';
|
|
21
|
-
*
|
|
22
|
-
* export default defineScript({
|
|
23
|
-
* props: {
|
|
24
|
-
* player: Props.String(),
|
|
25
|
-
* message: Props.String("Hello!"),
|
|
26
|
-
* },
|
|
27
|
-
* events: {
|
|
28
|
-
* onSuccess: Props.Boolean(),
|
|
29
|
-
* onError: Props.String(),
|
|
30
|
-
* },
|
|
31
|
-
* run({ process, props }) {
|
|
32
|
-
* const player = process.getPlayer(props.player);
|
|
33
|
-
* player.sendMessage(props.message);
|
|
34
|
-
* process.emit("onSuccess", true); // Typed!
|
|
35
|
-
* }
|
|
36
|
-
* });
|
|
37
|
-
*/
|
|
12
|
+
// Implementation
|
|
38
13
|
export function defineScript(config) {
|
|
39
14
|
// Simply return the config - the Java side will handle execution
|
|
40
15
|
// The return type is ExportedScript which combines:
|
|
41
|
-
// - Definition metadata (props, events, __type) for the Java runtime
|
|
16
|
+
// - Definition metadata (props, events, methods, __type) for the Java runtime
|
|
42
17
|
// - ScriptModule interface with .run(props) method for TypeScript imports
|
|
43
18
|
return config;
|
|
44
19
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug: Check what M resolves to
|
|
3
|
+
*/
|
|
4
|
+
import { defineScript } from '../../runtime.js';
|
|
5
|
+
const Props = {
|
|
6
|
+
String: (defaultValue) => ({ __brand: 'StringPropAPI', defaultValue }),
|
|
7
|
+
};
|
|
8
|
+
const result = defineScript({
|
|
9
|
+
props: {
|
|
10
|
+
message: Props.String("Hello!"),
|
|
11
|
+
},
|
|
12
|
+
methods: {
|
|
13
|
+
toto({ executor }) {
|
|
14
|
+
executor.asPlayer()?.sendMessage("toto!");
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
run({ methods }) {
|
|
18
|
+
// Let's see what type methods is
|
|
19
|
+
const m = methods;
|
|
20
|
+
const _check = m;
|
|
21
|
+
console.log(_check);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const _r = result;
|
|
25
|
+
console.log(_r);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* This simulates the EXACT user experience — calling defineScript()
|
|
5
|
+
* with methods and verifying autocomplete/type safety.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsc --noEmit
|
|
8
|
+
* If this file compiles, the DX is correct.
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("../index.js").ExportedScript<{
|
|
11
|
+
message: {
|
|
12
|
+
__brand: "StringPropAPI";
|
|
13
|
+
defaultValue: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
count: {
|
|
16
|
+
__brand: "NumberPropAPI";
|
|
17
|
+
defaultValue: number | undefined;
|
|
18
|
+
};
|
|
19
|
+
}, import("../index.js").EventSchema>;
|
|
20
|
+
export default _default;
|
|
21
|
+
export declare const noMethodsScript: import("../index.js").ExportedScript<{
|
|
22
|
+
name: {
|
|
23
|
+
__brand: "StringPropAPI";
|
|
24
|
+
defaultValue: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
}, import("../index.js").EmptyEventSchema>;
|
|
27
|
+
export declare const bareScript: import("../index.js").ExportedScript<Record<string, never>, import("../index.js").EmptyEventSchema>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* This simulates the EXACT user experience — calling defineScript()
|
|
5
|
+
* with methods and verifying autocomplete/type safety.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsc --noEmit
|
|
8
|
+
* If this file compiles, the DX is correct.
|
|
9
|
+
*/
|
|
10
|
+
import { defineScript } from '../../runtime.js';
|
|
11
|
+
// Simulate Props (these are runtime-injected, so we cast)
|
|
12
|
+
const Props = {
|
|
13
|
+
String: (defaultValue) => ({ __brand: 'StringPropAPI', defaultValue }),
|
|
14
|
+
Number: (defaultValue) => ({ __brand: 'NumberPropAPI', defaultValue }),
|
|
15
|
+
Boolean: (defaultValue) => ({ __brand: 'BooleanPropAPI', defaultValue }),
|
|
16
|
+
};
|
|
17
|
+
// ============================================
|
|
18
|
+
// Test: Full DX with methods
|
|
19
|
+
// ============================================
|
|
20
|
+
export default defineScript({
|
|
21
|
+
props: {
|
|
22
|
+
message: Props.String("Hello!"),
|
|
23
|
+
count: Props.Number(5),
|
|
24
|
+
},
|
|
25
|
+
methods: {
|
|
26
|
+
toto({ executor }) {
|
|
27
|
+
// ✅ executor is typed as ProcessExecutorAPI
|
|
28
|
+
executor.asPlayer()?.sendMessage("toto!");
|
|
29
|
+
},
|
|
30
|
+
greet({ executor, props }) {
|
|
31
|
+
// ✅ props is typed: props.message is string, props.count is number
|
|
32
|
+
const _msg = props.message;
|
|
33
|
+
const _cnt = props.count;
|
|
34
|
+
// Note: methods receive BaseScriptContext (no `methods` field)
|
|
35
|
+
// to avoid circular generic inference. Only run() gets methods.
|
|
36
|
+
executor.asPlayer()?.sendMessage("greet!");
|
|
37
|
+
},
|
|
38
|
+
doSomething({ executor }) {
|
|
39
|
+
// ✅ Methods receive BaseScriptContext with all subsystems
|
|
40
|
+
executor.asPlayer()?.sendMessage("doSomething!");
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
run({ executor, props, methods }) {
|
|
44
|
+
// ✅ props is typed
|
|
45
|
+
const _msg = props.message;
|
|
46
|
+
const _cnt = props.count;
|
|
47
|
+
// ✅ methods are typed with autocomplete
|
|
48
|
+
methods.toto();
|
|
49
|
+
methods.greet();
|
|
50
|
+
methods.doSomething();
|
|
51
|
+
// ✅ executor is typed
|
|
52
|
+
executor.asPlayer()?.sendMessage(props.message);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
// ============================================
|
|
56
|
+
// Test: Script without methods (backward compat)
|
|
57
|
+
// ============================================
|
|
58
|
+
export const noMethodsScript = defineScript({
|
|
59
|
+
props: {
|
|
60
|
+
name: Props.String("World"),
|
|
61
|
+
},
|
|
62
|
+
run({ props }) {
|
|
63
|
+
// ✅ props is typed
|
|
64
|
+
const _name = props.name;
|
|
65
|
+
console.log(_name);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
// ============================================
|
|
69
|
+
// Test: Script with no props and no methods
|
|
70
|
+
// ============================================
|
|
71
|
+
export const bareScript = defineScript({
|
|
72
|
+
run() {
|
|
73
|
+
// ✅ Compiles fine with no props/methods
|
|
74
|
+
console.log("bare script");
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Negative type test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* Uses @ts-expect-error to verify that invalid method calls
|
|
5
|
+
* produce type errors. If a @ts-expect-error line does NOT
|
|
6
|
+
* produce an error, tsc will report "Unused '@ts-expect-error' directive".
|
|
7
|
+
*
|
|
8
|
+
* Run: npx tsc --noEmit
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("../index.js").ExportedScript<{
|
|
11
|
+
message: {
|
|
12
|
+
__brand: "StringPropAPI";
|
|
13
|
+
defaultValue: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
}, import("../index.js").EventSchema>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Negative type test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* Uses @ts-expect-error to verify that invalid method calls
|
|
5
|
+
* produce type errors. If a @ts-expect-error line does NOT
|
|
6
|
+
* produce an error, tsc will report "Unused '@ts-expect-error' directive".
|
|
7
|
+
*
|
|
8
|
+
* Run: npx tsc --noEmit
|
|
9
|
+
*/
|
|
10
|
+
import { defineScript } from '../../runtime.js';
|
|
11
|
+
const Props = {
|
|
12
|
+
String: (defaultValue) => ({ __brand: 'StringPropAPI', defaultValue }),
|
|
13
|
+
};
|
|
14
|
+
export default defineScript({
|
|
15
|
+
props: {
|
|
16
|
+
message: Props.String("Hello!"),
|
|
17
|
+
},
|
|
18
|
+
methods: {
|
|
19
|
+
toto({ executor }) {
|
|
20
|
+
executor.asPlayer()?.sendMessage("toto!");
|
|
21
|
+
},
|
|
22
|
+
greet({ executor }) {
|
|
23
|
+
// Methods receive BaseScriptContext — no `methods` field.
|
|
24
|
+
// This avoids circular generic inference.
|
|
25
|
+
executor.asPlayer()?.sendMessage("greet!");
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
run({ methods }) {
|
|
29
|
+
methods.toto(); // ✅ valid
|
|
30
|
+
methods.greet(); // ✅ valid
|
|
31
|
+
// @ts-expect-error — 'nope' does not exist on methods
|
|
32
|
+
methods.nope();
|
|
33
|
+
// @ts-expect-error — 'doesNotExist' does not exist on methods
|
|
34
|
+
methods.doesNotExist();
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* This file is NOT executed — it only verifies that TypeScript
|
|
5
|
+
* correctly infers method types and provides autocomplete.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsc --noEmit
|
|
8
|
+
* If this file compiles, the types are correct.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level test for strongly-typed defineScript methods.
|
|
3
|
+
*
|
|
4
|
+
* This file is NOT executed — it only verifies that TypeScript
|
|
5
|
+
* correctly infers method types and provides autocomplete.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsc --noEmit
|
|
8
|
+
* If this file compiles, the types are correct.
|
|
9
|
+
*/
|
|
10
|
+
// These should be the stripped signatures:
|
|
11
|
+
const _test1 = {
|
|
12
|
+
greet: (name) => name, // ctx stripped, (name: string) => string
|
|
13
|
+
wave: () => { }, // ctx stripped, () => void
|
|
14
|
+
};
|
|
15
|
+
// Verify the config type accepts methods and that ctx.methods inside run() is strongly typed.
|
|
16
|
+
const _config = {
|
|
17
|
+
props: undefined,
|
|
18
|
+
methods: {
|
|
19
|
+
toto(ctx) {
|
|
20
|
+
ctx.executor.asPlayer()?.sendMessage("toto!");
|
|
21
|
+
},
|
|
22
|
+
greet(ctx) {
|
|
23
|
+
// Methods receive BaseScriptContext — no `methods` field
|
|
24
|
+
ctx.executor.asPlayer()?.sendMessage("greet!");
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
run(ctx) {
|
|
28
|
+
ctx.methods.toto();
|
|
29
|
+
ctx.methods.greet();
|
|
30
|
+
// ctx.props should be typed
|
|
31
|
+
const _msg = ctx.props.message;
|
|
32
|
+
const _cnt = ctx.props.count;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const _emptyCtx = {};
|
|
36
|
+
// Should be Record<string, never> — no methods
|
|
37
|
+
const _emptyWrapped = {};
|
|
38
|
+
console.log("Type tests passed (compile-time only)", _test1, _config, _emptyCtx, _emptyWrapped);
|
|
39
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -97,6 +97,22 @@ export type EmptyEventSchema = Record<string, never>;
|
|
|
97
97
|
* Infer the event payload type from an EventSchema.
|
|
98
98
|
*/
|
|
99
99
|
export type InferEventPayload<E extends EventSchema, K extends keyof E> = InferPropType<E[K]>;
|
|
100
|
+
/**
|
|
101
|
+
* Strips the first ctx parameter from each method, producing the type
|
|
102
|
+
* available as ctx.methods inside scripts.
|
|
103
|
+
*
|
|
104
|
+
* Given methods like `{ greet(ctx: ScriptContext, ...args): void }`,
|
|
105
|
+
* produces `{ greet(...args): void }` — ctx is auto-injected at runtime.
|
|
106
|
+
*/
|
|
107
|
+
export type WrappedMethods<M> = {
|
|
108
|
+
[K in keyof M]: M[K] extends (ctx: never, ...args: infer A) => infer R ? (...args: A) => R : () => unknown;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Script configuration for defineScript() — without methods.
|
|
112
|
+
*
|
|
113
|
+
* @typeParam P - Props schema type
|
|
114
|
+
* @typeParam E - Events schema type
|
|
115
|
+
*/
|
|
100
116
|
export interface ScriptConfig<P extends PropSchema, E extends EventSchema = EmptyEventSchema> {
|
|
101
117
|
/** Props schema with types and defaults */
|
|
102
118
|
props?: P;
|
|
@@ -124,6 +140,47 @@ export interface ScriptConfig<P extends PropSchema, E extends EventSchema = Empt
|
|
|
124
140
|
*/
|
|
125
141
|
run: (this: TypedMcModuleAPI<E>, ctx: ScriptContext<InferProps<P>, E>) => void;
|
|
126
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Script configuration for defineScript() — with strongly-typed methods.
|
|
145
|
+
*
|
|
146
|
+
* Uses a generic `M` to capture the literal shape of the methods object.
|
|
147
|
+
* Each method receives a `ScriptContext` with `WrappedMethods<M>` providing
|
|
148
|
+
* full autocomplete for `ctx.methods.*`.
|
|
149
|
+
*
|
|
150
|
+
* The `methods` field uses a mapped type over `keyof M` so TypeScript
|
|
151
|
+
* can infer `M` from the object literal keys, then constrains each
|
|
152
|
+
* method's signature.
|
|
153
|
+
*
|
|
154
|
+
* @typeParam P - Props schema type
|
|
155
|
+
* @typeParam E - Events schema type
|
|
156
|
+
* @typeParam M - Methods record type (inferred from the methods object literal)
|
|
157
|
+
*/
|
|
158
|
+
/** Constraint for the methods record — each method receives BaseScriptContext */
|
|
159
|
+
export type MethodMap<P extends PropSchema, E extends EventSchema> = Record<string, (ctx: BaseScriptContext<InferProps<P>, E>, ...args: unknown[]) => unknown>;
|
|
160
|
+
export type ScriptConfigWithMethods<P extends PropSchema, E extends EventSchema, M extends MethodMap<P, E>> = {
|
|
161
|
+
props?: P;
|
|
162
|
+
events?: E;
|
|
163
|
+
/**
|
|
164
|
+
* Named methods that can be called externally via `process.call("methodName", ...args)`
|
|
165
|
+
* or internally via `ctx.methods.methodName()`.
|
|
166
|
+
*
|
|
167
|
+
* Each method receives `(ctx: BaseScriptContext, ...extraArgs)` — a context without `methods`
|
|
168
|
+
* to avoid circular generic inference. At runtime, Java injects the full context.
|
|
169
|
+
*
|
|
170
|
+
* When called via `ctx.methods` in `run()`, the `ctx` parameter is auto-injected.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* methods: {
|
|
175
|
+
* greet({ executor }) {
|
|
176
|
+
* executor.asPlayer()?.sendMessage("Hello!");
|
|
177
|
+
* },
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
methods: M;
|
|
182
|
+
run: (this: TypedMcModuleAPI<E>, ctx: ScriptContext<InferProps<P>, E, WrappedMethods<M>>) => void;
|
|
183
|
+
};
|
|
127
184
|
export interface ScriptDefinition<P extends PropSchema, E extends EventSchema = EmptyEventSchema> {
|
|
128
185
|
props?: P;
|
|
129
186
|
events?: E;
|
|
@@ -260,10 +317,14 @@ export interface ItemDefinition extends ItemConfig {
|
|
|
260
317
|
readonly __type: "item";
|
|
261
318
|
}
|
|
262
319
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
320
|
+
* Base context passed to method functions.
|
|
321
|
+
* Same as ScriptContext but without `methods` to avoid circular generic inference.
|
|
322
|
+
* At runtime, Java actually injects the full context including methods.
|
|
323
|
+
*
|
|
324
|
+
* @typeParam P - Inferred props type
|
|
325
|
+
* @typeParam E - Event schema type
|
|
265
326
|
*/
|
|
266
|
-
export interface
|
|
327
|
+
export interface BaseScriptContext<P = Record<string, unknown>, E extends EventSchema = EmptyEventSchema> {
|
|
267
328
|
/** Process API for script management (typed for events if declared) */
|
|
268
329
|
process: TypedMcModuleAPI<E>;
|
|
269
330
|
/** Validated props */
|
|
@@ -289,6 +350,33 @@ export interface ScriptContext<P = Record<string, unknown>, E extends EventSchem
|
|
|
289
350
|
/** Persistent storage system */
|
|
290
351
|
storage: StorageAPI;
|
|
291
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Full context passed to the script `run()` function.
|
|
355
|
+
* Extends BaseScriptContext with strongly-typed `methods`.
|
|
356
|
+
*
|
|
357
|
+
* @typeParam P - Inferred props type
|
|
358
|
+
* @typeParam E - Event schema type
|
|
359
|
+
* @typeParam Methods - Strongly-typed methods map (auto-inferred from defineScript)
|
|
360
|
+
*/
|
|
361
|
+
export interface ScriptContext<P = Record<string, unknown>, E extends EventSchema = EmptyEventSchema, Methods = Record<string, never>> extends BaseScriptContext<P, E> {
|
|
362
|
+
/**
|
|
363
|
+
* Callable methods defined in defineScript({ methods: { ... } }).
|
|
364
|
+
* These are wrapped so `ctx` is auto-injected — call them directly without passing ctx.
|
|
365
|
+
*
|
|
366
|
+
* Fully typed: autocomplete shows only the methods you defined,
|
|
367
|
+
* and calling a non-existent method is a type error.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* run({ methods }) {
|
|
372
|
+
* methods.greet(); // ✅ ctx is auto-injected, autocomplete works
|
|
373
|
+
* methods.farewell("arg"); // ✅ extra args passed through
|
|
374
|
+
* methods.nope(); // ❌ type error — not defined
|
|
375
|
+
* }
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
methods: Methods;
|
|
379
|
+
}
|
|
292
380
|
export interface PropSchema {
|
|
293
381
|
[key: string]: PropAPI;
|
|
294
382
|
}
|