@silkweaver/build 1.0.0 → 1.2.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/dist/build.d.ts +10 -0
- package/dist/build.js +140 -14
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/object_format.d.ts +31 -1
- package/dist/object_format.js +283 -2
- package/dist/templates.d.ts +24 -0
- package/dist/templates.js +114 -0
- package/package.json +5 -4
- package/templates/empty/project.json +27 -0
- package/templates/empty/rooms/room_main/room.json +16 -0
- package/templates/platformer/objects/_col.ts +3 -0
- package/templates/platformer/objects/obj_platform.ts +10 -0
- package/templates/platformer/objects/obj_player.ts +73 -0
- package/templates/platformer/project.json +44 -0
- package/templates/platformer/rooms/room_main/room.json +327 -0
- package/templates/platformer/sprites/spr_platform/0.png +0 -0
- package/templates/platformer/sprites/spr_platform/meta.json +17 -0
- package/templates/platformer/sprites/spr_player/0.png +0 -0
- package/templates/platformer/sprites/spr_player/meta.json +17 -0
- package/templates/topdown/objects/_col.ts +2 -0
- package/templates/topdown/objects/obj_player.ts +45 -0
- package/templates/topdown/objects/obj_wall.ts +6 -0
- package/templates/topdown/project.json +44 -0
- package/templates/topdown/rooms/room_main/room.json +727 -0
- package/templates/topdown/sprites/spr_player/0.png +0 -0
- package/templates/topdown/sprites/spr_player/meta.json +17 -0
- package/templates/topdown/sprites/spr_wall/0.png +0 -0
- package/templates/topdown/sprites/spr_wall/meta.json +17 -0
package/dist/build.d.ts
CHANGED
|
@@ -15,6 +15,16 @@ import type { project_file as project_data } from '@silkweaver/project';
|
|
|
15
15
|
export type { project_data };
|
|
16
16
|
/** Reads and parses a project's project.json. */
|
|
17
17
|
export declare function read_project(project_folder: string): Promise<project_data>;
|
|
18
|
+
/** Public: the engine version this toolchain ships — what "Update engine" would pin a project to. */
|
|
19
|
+
export declare function toolchain_engine_version(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Vendors the toolchain's current engine into a project as one self-contained bundle
|
|
22
|
+
* (`.engine/engine.mjs`, matter-js inlined) and records its version (`.engine/version.json` +
|
|
23
|
+
* project.json `engineVersion`). Called at project creation; safe to re-run to upgrade the pin.
|
|
24
|
+
* @param project_folder - Absolute path to the project folder
|
|
25
|
+
* @returns The vendored engine version.
|
|
26
|
+
*/
|
|
27
|
+
export declare function vendor_engine(project_folder: string): Promise<string>;
|
|
18
28
|
/**
|
|
19
29
|
* Builds the game to a single bundled game.js at out_path, for the in-IDE preview.
|
|
20
30
|
* @param out_path - Where to write game.js (the caller decides — e.g. exports/game.js)
|
package/dist/build.js
CHANGED
|
@@ -47,6 +47,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
47
47
|
})();
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
49
|
exports.read_project = read_project;
|
|
50
|
+
exports.toolchain_engine_version = toolchain_engine_version;
|
|
51
|
+
exports.vendor_engine = vendor_engine;
|
|
50
52
|
exports.build_preview = build_preview;
|
|
51
53
|
exports.export_html5 = export_html5;
|
|
52
54
|
exports.export_executable = export_executable;
|
|
@@ -54,6 +56,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
54
56
|
const path = __importStar(require("node:path"));
|
|
55
57
|
const os = __importStar(require("node:os"));
|
|
56
58
|
const node_url_1 = require("node:url");
|
|
59
|
+
const object_format_js_1 = require("./object_format.js");
|
|
57
60
|
/** Reads and parses a project's project.json. */
|
|
58
61
|
async function read_project(project_folder) {
|
|
59
62
|
const proj_text = await fs.promises.readFile(path.join(project_folder, 'project.json'), 'utf8');
|
|
@@ -64,18 +67,73 @@ async function read_project(project_folder) {
|
|
|
64
67
|
* entry can import the whole API (keeping it in sync with the IDE's autocomplete).
|
|
65
68
|
* esbuild tree-shakes whatever the game doesn't actually use.
|
|
66
69
|
*/
|
|
67
|
-
|
|
70
|
+
const _engine_names = new Map(); // engine path → its export names (per vendored engine)
|
|
68
71
|
async function engine_export_names(engine_path) {
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
const cached = _engine_names.get(engine_path);
|
|
73
|
+
if (cached)
|
|
74
|
+
return cached;
|
|
71
75
|
const mod = await import((0, node_url_1.pathToFileURL)(engine_path).href);
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const names = Object.keys(mod).filter(n => n !== 'default' && /^[A-Za-z_$][\w$]*$/.test(n));
|
|
77
|
+
_engine_names.set(engine_path, names);
|
|
78
|
+
return names;
|
|
74
79
|
}
|
|
75
|
-
/** Absolute path to the engine
|
|
80
|
+
/** Absolute path to the toolchain's own engine entry (the vendoring source + pre-vendoring fallback). */
|
|
76
81
|
function engine_entry() {
|
|
77
82
|
return require.resolve('@silkweaver/engine');
|
|
78
83
|
}
|
|
84
|
+
/** The version of the toolchain's own engine (what a freshly-vendored project pins). */
|
|
85
|
+
function engine_version() {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(fs.readFileSync(path.join(path.dirname(engine_entry()), '..', 'package.json'), 'utf8')).version;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return '0.0.0';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Public: the engine version this toolchain ships — what "Update engine" would pin a project to. */
|
|
94
|
+
function toolchain_engine_version() {
|
|
95
|
+
return engine_version();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolves the engine a project builds against: its vendored copy (`.engine/engine.mjs`) when
|
|
99
|
+
* present, else the toolchain's own engine (projects created before per-project vendoring). This is
|
|
100
|
+
* what lets a project keep building against the exact engine it was made with — IDE updates don't
|
|
101
|
+
* touch it.
|
|
102
|
+
*/
|
|
103
|
+
function resolve_engine(project_folder) {
|
|
104
|
+
const vendored = path.join(project_folder, '.engine', 'engine.mjs');
|
|
105
|
+
return fs.existsSync(vendored) ? vendored : engine_entry();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Vendors the toolchain's current engine into a project as one self-contained bundle
|
|
109
|
+
* (`.engine/engine.mjs`, matter-js inlined) and records its version (`.engine/version.json` +
|
|
110
|
+
* project.json `engineVersion`). Called at project creation; safe to re-run to upgrade the pin.
|
|
111
|
+
* @param project_folder - Absolute path to the project folder
|
|
112
|
+
* @returns The vendored engine version.
|
|
113
|
+
*/
|
|
114
|
+
async function vendor_engine(project_folder) {
|
|
115
|
+
const version = engine_version();
|
|
116
|
+
const dir = path.join(project_folder, '.engine');
|
|
117
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
118
|
+
const esbuild_api = require('esbuild');
|
|
119
|
+
await esbuild_api.build({
|
|
120
|
+
entryPoints: [engine_entry()],
|
|
121
|
+
bundle: true,
|
|
122
|
+
format: 'esm',
|
|
123
|
+
outfile: path.join(dir, 'engine.mjs'),
|
|
124
|
+
keepNames: true, // the engine reads constructor.name at runtime
|
|
125
|
+
});
|
|
126
|
+
await fs.promises.writeFile(path.join(dir, 'version.json'), JSON.stringify({ version, vendoredAt: new Date().toISOString() }, null, 2) + '\n', 'utf8');
|
|
127
|
+
// Pin the version in project.json so the IDE can display it / gate features on it.
|
|
128
|
+
try {
|
|
129
|
+
const proj_path = path.join(project_folder, 'project.json');
|
|
130
|
+
const proj = JSON.parse(await fs.promises.readFile(proj_path, 'utf8'));
|
|
131
|
+
proj.engineVersion = version;
|
|
132
|
+
await fs.promises.writeFile(proj_path, JSON.stringify(proj, null, 2) + '\n', 'utf8');
|
|
133
|
+
}
|
|
134
|
+
catch { /* no project.json yet — caller sets engineVersion */ }
|
|
135
|
+
return version;
|
|
136
|
+
}
|
|
79
137
|
/**
|
|
80
138
|
* Strips leading `import …` lines from a code snippet so it can be inlined inside a
|
|
81
139
|
* function body (timeline moments). Engine references auto-resolve via the inject shim,
|
|
@@ -100,23 +158,25 @@ function hex_to_bgr(hex) {
|
|
|
100
158
|
* Generates the bootstrapper (_entry.ts) source for a project.
|
|
101
159
|
* @param asset_mode - 'preview' (file:// into project) or 'export' (relative assets/)
|
|
102
160
|
*/
|
|
103
|
-
async function generate_entry_code(project_folder, proj, asset_mode) {
|
|
104
|
-
|
|
161
|
+
async function generate_entry_code(project_folder, proj, asset_mode, engine_path) {
|
|
162
|
+
// Parent-before-child so generated imports/registrations init objects in dependency order.
|
|
163
|
+
const object_names = await _objects_parent_first(project_folder, Object.keys(proj.resources.objects ?? {}));
|
|
105
164
|
const room_names = Object.keys(proj.resources.rooms ?? {});
|
|
106
165
|
const sprite_names = Object.keys(proj.resources.sprites ?? {});
|
|
107
166
|
const script_names = Object.keys(proj.resources.scripts ?? {});
|
|
108
167
|
const font_names = Object.keys(proj.resources.fonts ?? {});
|
|
109
168
|
const path_names = Object.keys(proj.resources.paths ?? {});
|
|
110
169
|
const timeline_names = Object.keys(proj.resources.timelines ?? {});
|
|
111
|
-
//
|
|
112
|
-
const engine_path = engine_entry();
|
|
170
|
+
// engine_path is whatever the project resolves to — its vendored copy, or the toolchain's.
|
|
113
171
|
// Each object is a single class file: objects/<name>.ts (a gm_object subclass).
|
|
114
172
|
// It is imported as-is; metadata/variables/events all live in the class.
|
|
115
173
|
const object_imports = [];
|
|
174
|
+
const object_registrations = []; // object_register_name('x', x) → resolves object_get('x')
|
|
116
175
|
for (const obj_name of object_names) {
|
|
117
176
|
const class_file = path.join(project_folder, 'objects', `${obj_name}.ts`);
|
|
118
177
|
if (await _path_exists(class_file)) {
|
|
119
178
|
object_imports.push(`import { ${obj_name} } from '${class_file.replace(/\\/g, '/')}'`);
|
|
179
|
+
object_registrations.push(`object_register_name('${obj_name}', ${obj_name})`);
|
|
120
180
|
}
|
|
121
181
|
else {
|
|
122
182
|
console.warn(`[build] object '${obj_name}' has no objects/${obj_name}.ts — skipped`);
|
|
@@ -323,6 +383,9 @@ ${engine_import}
|
|
|
323
383
|
|
|
324
384
|
${object_imports.join('\n')}
|
|
325
385
|
|
|
386
|
+
// Register objects by name so object_get('name') resolves them at runtime.
|
|
387
|
+
${object_registrations.join('\n')}
|
|
388
|
+
|
|
326
389
|
${script_imports}
|
|
327
390
|
|
|
328
391
|
// ── Sprite loader ───────────────────────────────────────────────────────────
|
|
@@ -485,7 +548,8 @@ ${room_physics[start_room] ? ` physics_world_create(${room_physics[start_room
|
|
|
485
548
|
* ESM game.js at out_path via esbuild, then removes the temporary file.
|
|
486
549
|
*/
|
|
487
550
|
async function bundle_game(project_folder, proj, asset_mode, out_path, minify) {
|
|
488
|
-
const
|
|
551
|
+
const engine_path = resolve_engine(project_folder);
|
|
552
|
+
const entry_code = await generate_entry_code(project_folder, proj, asset_mode, engine_path);
|
|
489
553
|
const entry_path = path.join(project_folder, '_entry.ts');
|
|
490
554
|
const globals_path = path.join(project_folder, '_engine_globals.ts');
|
|
491
555
|
await fs.promises.writeFile(entry_path, entry_code, 'utf8');
|
|
@@ -493,8 +557,24 @@ async function bundle_game(project_folder, proj, asset_mode, out_path, minify) {
|
|
|
493
557
|
// `inject`, it makes any *bare* engine reference in an object/script file (e.g. `gm_object`,
|
|
494
558
|
// `draw_sprite`) resolve to an auto-injected import — tree-shaken — so users never write or
|
|
495
559
|
// manage `import … from '@silkweaver/engine'` themselves.
|
|
496
|
-
const engine_names = await engine_export_names(
|
|
497
|
-
|
|
560
|
+
const engine_names = await engine_export_names(engine_path);
|
|
561
|
+
// Also re-export the project's OBJECT classes by name, so they can be referenced bare —
|
|
562
|
+
// GMS-style, e.g. `place_meeting(x, y, obj_wall)` or `static parent = par_solid` — with no
|
|
563
|
+
// import (matching what the IDE editor already declares). The defining file won't self-import;
|
|
564
|
+
// skip any object whose name would collide with an engine export.
|
|
565
|
+
// Parent-before-child order: the re-export order here becomes the module init order, so an
|
|
566
|
+
// object's `static parent = par` resolves to a defined class instead of undefined.
|
|
567
|
+
const object_order = await _objects_parent_first(project_folder, Object.keys(proj.resources.objects ?? {}));
|
|
568
|
+
const object_lines = [];
|
|
569
|
+
for (const obj_name of object_order) {
|
|
570
|
+
if (engine_names.includes(obj_name))
|
|
571
|
+
continue;
|
|
572
|
+
const class_file = path.join(project_folder, 'objects', `${obj_name}.ts`);
|
|
573
|
+
if (await _path_exists(class_file)) {
|
|
574
|
+
object_lines.push(`export { ${obj_name} } from '${class_file.replace(/\\/g, '/')}'`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
await fs.promises.writeFile(globals_path, `export { ${engine_names.join(', ')} } from '@silkweaver/engine'\n${object_lines.join('\n')}\n`, 'utf8');
|
|
498
578
|
try {
|
|
499
579
|
// Run esbuild via its Node API. Alias the package specifier to the resolved engine
|
|
500
580
|
// entry so the generated entry AND any class-per-object files resolve to the same
|
|
@@ -506,7 +586,11 @@ async function bundle_game(project_folder, proj, asset_mode, out_path, minify) {
|
|
|
506
586
|
outfile: out_path,
|
|
507
587
|
format: 'esm',
|
|
508
588
|
minify,
|
|
509
|
-
|
|
589
|
+
// Preserve class/function `.name` even when minifying — the engine reads
|
|
590
|
+
// `this.constructor.name` (resource.name, room type checks, object_get_name),
|
|
591
|
+
// so identifier mangling would otherwise break exported (minified) games.
|
|
592
|
+
keepNames: true,
|
|
593
|
+
alias: { '@silkweaver/engine': engine_path },
|
|
510
594
|
inject: [globals_path],
|
|
511
595
|
});
|
|
512
596
|
}
|
|
@@ -686,6 +770,48 @@ async function _path_exists(p) {
|
|
|
686
770
|
return false;
|
|
687
771
|
}
|
|
688
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Orders object names so every object's `static parent` (when it's another project object)
|
|
775
|
+
* appears BEFORE it. esbuild lazy-initializes the object modules (the inject shim and each
|
|
776
|
+
* object form an import cycle), running them in declaration order inside one initializer; a
|
|
777
|
+
* child whose `static parent = par` is evaluated before `par`'s module would capture
|
|
778
|
+
* `undefined`. Initializing parents first makes the reference resolve to the real class.
|
|
779
|
+
* A parent cycle (user error) is broken gracefully so this never loops.
|
|
780
|
+
*/
|
|
781
|
+
async function _objects_parent_first(project_folder, names) {
|
|
782
|
+
const present = new Set(names);
|
|
783
|
+
const parent_of = new Map();
|
|
784
|
+
for (const name of names) {
|
|
785
|
+
const file = path.join(project_folder, 'objects', `${name}.ts`);
|
|
786
|
+
let parent;
|
|
787
|
+
if (await _path_exists(file)) {
|
|
788
|
+
try {
|
|
789
|
+
const p = (0, object_format_js_1.parse_object)(await fs.promises.readFile(file, 'utf8')).parent;
|
|
790
|
+
if (p && p !== name && present.has(p))
|
|
791
|
+
parent = p;
|
|
792
|
+
}
|
|
793
|
+
catch { /* unparseable — treat as no parent */ }
|
|
794
|
+
}
|
|
795
|
+
parent_of.set(name, parent);
|
|
796
|
+
}
|
|
797
|
+
const out = [];
|
|
798
|
+
const done = new Set();
|
|
799
|
+
const onstack = new Set();
|
|
800
|
+
const visit = (n) => {
|
|
801
|
+
if (done.has(n) || onstack.has(n))
|
|
802
|
+
return; // onstack guard breaks any parent cycle
|
|
803
|
+
onstack.add(n);
|
|
804
|
+
const p = parent_of.get(n);
|
|
805
|
+
if (p)
|
|
806
|
+
visit(p);
|
|
807
|
+
onstack.delete(n);
|
|
808
|
+
done.add(n);
|
|
809
|
+
out.push(n);
|
|
810
|
+
};
|
|
811
|
+
for (const n of names)
|
|
812
|
+
visit(n);
|
|
813
|
+
return out;
|
|
814
|
+
}
|
|
689
815
|
/** Recursively copies a directory tree from src to dst. */
|
|
690
816
|
async function _copy_dir(src, dst) {
|
|
691
817
|
await fs.promises.mkdir(dst, { recursive: true });
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - export_executable → a packaged desktop application
|
|
8
8
|
*
|
|
9
9
|
* Also re-exports the class-file object read/write layer (object_format) used by the
|
|
10
|
-
* IDE's object editor
|
|
10
|
+
* IDE's object editor, and the starter-template materializer (templates). Pure Node —
|
|
11
|
+
* drives both the IDE (over IPC) and the CLI.
|
|
11
12
|
*/
|
|
12
13
|
export * from './build.js';
|
|
13
14
|
export * from './object_format.js';
|
|
15
|
+
export * from './templates.js';
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* - export_executable → a packaged desktop application
|
|
9
9
|
*
|
|
10
10
|
* Also re-exports the class-file object read/write layer (object_format) used by the
|
|
11
|
-
* IDE's object editor
|
|
11
|
+
* IDE's object editor, and the starter-template materializer (templates). Pure Node —
|
|
12
|
+
* drives both the IDE (over IPC) and the CLI.
|
|
12
13
|
*/
|
|
13
14
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
15
|
if (k2 === undefined) k2 = k;
|
|
@@ -27,3 +28,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
27
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
29
|
__exportStar(require("./build.js"), exports);
|
|
29
30
|
__exportStar(require("./object_format.js"), exports);
|
|
31
|
+
__exportStar(require("./templates.js"), exports);
|
package/dist/object_format.d.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** Known static-metadata field names an object class may declare. */
|
|
13
13
|
export type meta_field = 'sprite' | 'solid' | 'visible' | 'persistent' | 'depth' | 'parent';
|
|
14
|
+
/** Canonical GMS-style order of `on_*` event methods (used to keep events ordered in code). */
|
|
15
|
+
export declare const EVENT_ORDER: string[];
|
|
14
16
|
export interface object_model {
|
|
15
17
|
class_name: string;
|
|
16
18
|
sprite: string | null;
|
|
@@ -27,6 +29,14 @@ export interface object_model {
|
|
|
27
29
|
}
|
|
28
30
|
/** Extracts the object model from a class-file source. */
|
|
29
31
|
export declare function parse_object(src: string): object_model;
|
|
32
|
+
/**
|
|
33
|
+
* Collects every instance member reachable through `this.` in a class: declared instance fields
|
|
34
|
+
* *plus* members created/assigned at runtime inside any event (e.g. `this.velocity = 0` in on_create,
|
|
35
|
+
* read in on_step). This drives `this.` autocomplete, so a variable made in one event surfaces in all
|
|
36
|
+
* the others — matching the usual split of constants on the object, runtime state in Create. Static
|
|
37
|
+
* fields and method/event names are excluded (engine members are merged in separately).
|
|
38
|
+
*/
|
|
39
|
+
export declare function this_members(src: string): string[];
|
|
30
40
|
/**
|
|
31
41
|
* Sets (adds or updates) a static metadata field. `expr` is the raw initializer
|
|
32
42
|
* text (e.g. `'spr_player'`, `true`, `-5`, `obj_base`).
|
|
@@ -38,9 +48,29 @@ export declare function remove_static(src: string, name: meta_field): string;
|
|
|
38
48
|
export declare function set_field(src: string, name: string, expr: string): string;
|
|
39
49
|
/** Removes an instance variable field. */
|
|
40
50
|
export declare function remove_field(src: string, name: string): string;
|
|
41
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Adds an `on_*` event method stub if it is not already present, inserted in canonical event
|
|
53
|
+
* order (before the first existing event method that comes later in EVENT_ORDER).
|
|
54
|
+
*/
|
|
42
55
|
export declare function add_method(src: string, method: string, params?: string, body?: string): string;
|
|
43
56
|
/** Removes an event method by name. */
|
|
44
57
|
export declare function remove_method(src: string, method: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Returns one event method's body as an editable, de-indented buffer, or null if absent/bodyless.
|
|
60
|
+
* @param method - The on_* method to extract (e.g. 'on_step')
|
|
61
|
+
*/
|
|
62
|
+
export declare function get_event_body(src: string, method: string): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Replaces an event method's body with `body` (an edited, de-indented buffer), re-indenting it to
|
|
65
|
+
* the method's level. Returns the full updated source; a no-op if the method is absent.
|
|
66
|
+
* @param method - The on_* method to update
|
|
67
|
+
* @param body - The new body (column-0 / de-indented, as edited)
|
|
68
|
+
*/
|
|
69
|
+
export declare function set_event_body(src: string, method: string, body: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Normalizes a class-per-object file for display in the full code view: variables hoisted above all
|
|
72
|
+
* events, canonical event order, then consistent indentation. Unchanged if there's no class.
|
|
73
|
+
*/
|
|
74
|
+
export declare function normalize_object(src: string): string;
|
|
45
75
|
/** Generates a minimal class-file source for a new object. Imports are auto-managed (none needed). */
|
|
46
76
|
export declare function scaffold_object(class_name: string): string;
|