@aethervtt/shard-node 0.0.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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/bundle.d.ts +53 -0
- package/dist/bundle.js +137 -0
- package/dist/generators.d.ts +53 -0
- package/dist/generators.js +161 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +331 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Dias
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ShardError } from '@aethervtt/shard-core';
|
|
2
|
+
import { type SourceMap } from '@aethervtt/shard-project';
|
|
3
|
+
export interface BuiltBundle {
|
|
4
|
+
/** Content hash of the bundle. */
|
|
5
|
+
hash: string;
|
|
6
|
+
/** Absolute path of the written file. */
|
|
7
|
+
file: string;
|
|
8
|
+
/** `file://` URL, for `import()`. */
|
|
9
|
+
url: string;
|
|
10
|
+
code: string;
|
|
11
|
+
map: SourceMap | undefined;
|
|
12
|
+
ms: number;
|
|
13
|
+
/** Inputs and their imports (esbuild's metafile): generators' code hashes come from it. */
|
|
14
|
+
graph: {
|
|
15
|
+
inputs: Record<string, {
|
|
16
|
+
imports: {
|
|
17
|
+
path: string;
|
|
18
|
+
external?: boolean;
|
|
19
|
+
}[];
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface BundlerOptions {
|
|
24
|
+
/** Project folder. */
|
|
25
|
+
root: string;
|
|
26
|
+
/** Entry module, project-relative (the manifest's `entry`). */
|
|
27
|
+
entry: string;
|
|
28
|
+
/** Where bundles are written. Default `.shard/build`. */
|
|
29
|
+
outDir?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bundles a project's entry into one ESM file with `@aethervtt/shard-*` left external, so the engine's module
|
|
33
|
+
* instances are shared. Keeps an incremental esbuild context for fast rebuilds.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createBundler(options: BundlerOptions): Promise<{
|
|
36
|
+
root: string;
|
|
37
|
+
readonly last: BuiltBundle | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* A URL for importing a bundle that no module cache has seen. Identical code rebuilds to the same
|
|
40
|
+
* file (content hash), and without this a revert would import a stale, earlier module instance.
|
|
41
|
+
*/
|
|
42
|
+
importUrl(bundle: BuiltBundle): string;
|
|
43
|
+
build: () => Promise<BuiltBundle>;
|
|
44
|
+
/**
|
|
45
|
+
* Rebuilds (debounced) when anything under the entry's folder changes. `onBuild` gets the bundle
|
|
46
|
+
* or the error. Returns a stop function.
|
|
47
|
+
*/
|
|
48
|
+
watch(onBuild: (result: BuiltBundle | ShardError) => void, debounceMs?: number): () => void;
|
|
49
|
+
/** Removes bundles other than the latest from the output folder. */
|
|
50
|
+
prune(): Promise<void>;
|
|
51
|
+
dispose: () => Promise<void>;
|
|
52
|
+
}>;
|
|
53
|
+
export type Bundler = Awaited<ReturnType<typeof createBundler>>;
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { watch as fsWatch } from "node:fs";
|
|
3
|
+
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { builtinModules } from "node:module";
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
8
|
+
import { inlineSourceMap } from "@aethervtt/shard-project";
|
|
9
|
+
import * as esbuild from "esbuild";
|
|
10
|
+
const BUILTINS = new Set(builtinModules.flatMap((m) => [m, `node:${m}`]));
|
|
11
|
+
const noNodeBuiltins = {
|
|
12
|
+
name: "shard-no-node-builtins",
|
|
13
|
+
setup(build) {
|
|
14
|
+
build.onResolve({ filter: /^[a-z:/_]+$/ }, (args) => {
|
|
15
|
+
if (!BUILTINS.has(args.path)) return void 0;
|
|
16
|
+
return {
|
|
17
|
+
errors: [
|
|
18
|
+
{
|
|
19
|
+
text: `"${args.path}" is a Node built-in; project code must run in browsers too`,
|
|
20
|
+
detail: "project/node-builtin"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function bundleError(root, failure) {
|
|
28
|
+
const first = failure.errors?.[0];
|
|
29
|
+
if (!first) {
|
|
30
|
+
return new ShardError("project/bundle-failed", failure.message, { cause: failure });
|
|
31
|
+
}
|
|
32
|
+
const code = first.detail === "project/node-builtin" ? "project/node-builtin" : "project/bundle-failed";
|
|
33
|
+
const loc = first.location;
|
|
34
|
+
const file = loc ? relative(root, resolve(root, loc.file)).split("\\").join("/") : void 0;
|
|
35
|
+
const source = loc ? `${file}:${loc.line}:${loc.column + 1}` : void 0;
|
|
36
|
+
const err = new ShardError(code, source ? `${source}: ${first.text}` : first.text, {
|
|
37
|
+
path: file,
|
|
38
|
+
hint: code === "project/node-builtin" ? "Use @aethervtt/shard-platform services (files, storage) instead of Node APIs." : "Fix the error and save; the last good code keeps running.",
|
|
39
|
+
cause: failure
|
|
40
|
+
});
|
|
41
|
+
return Object.assign(err, { source });
|
|
42
|
+
}
|
|
43
|
+
async function createBundler(options) {
|
|
44
|
+
const root = resolve(options.root);
|
|
45
|
+
const outDir = resolve(root, options.outDir ?? ".shard/build");
|
|
46
|
+
let last;
|
|
47
|
+
const context = await esbuild.context({
|
|
48
|
+
absWorkingDir: root,
|
|
49
|
+
entryPoints: [resolve(root, options.entry)],
|
|
50
|
+
// Only names the output so source-map paths are relative to it; nothing is written by esbuild.
|
|
51
|
+
outfile: join(outDir, "main.mjs"),
|
|
52
|
+
bundle: true,
|
|
53
|
+
format: "esm",
|
|
54
|
+
platform: "browser",
|
|
55
|
+
target: "es2023",
|
|
56
|
+
sourcemap: "inline",
|
|
57
|
+
sourcesContent: false,
|
|
58
|
+
external: ["@aethervtt/shard-*"],
|
|
59
|
+
write: false,
|
|
60
|
+
metafile: true,
|
|
61
|
+
logLevel: "silent",
|
|
62
|
+
plugins: [noNodeBuiltins]
|
|
63
|
+
});
|
|
64
|
+
const build = async () => {
|
|
65
|
+
const start = performance.now();
|
|
66
|
+
let result;
|
|
67
|
+
try {
|
|
68
|
+
result = await context.rebuild();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw bundleError(root, err);
|
|
71
|
+
}
|
|
72
|
+
const code = result.outputFiles[0].text;
|
|
73
|
+
const hash = createHash("sha256").update(code).digest("hex").slice(0, 16);
|
|
74
|
+
const file = join(outDir, `main.${hash}.mjs`);
|
|
75
|
+
if (last?.hash !== hash) {
|
|
76
|
+
await mkdir(outDir, { recursive: true });
|
|
77
|
+
await writeFile(file, code);
|
|
78
|
+
}
|
|
79
|
+
last = {
|
|
80
|
+
hash,
|
|
81
|
+
file,
|
|
82
|
+
url: pathToFileURL(file).href,
|
|
83
|
+
code,
|
|
84
|
+
map: inlineSourceMap(code),
|
|
85
|
+
ms: performance.now() - start,
|
|
86
|
+
graph: result.metafile
|
|
87
|
+
};
|
|
88
|
+
return last;
|
|
89
|
+
};
|
|
90
|
+
let imports = 0;
|
|
91
|
+
return {
|
|
92
|
+
root,
|
|
93
|
+
get last() {
|
|
94
|
+
return last;
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* A URL for importing a bundle that no module cache has seen. Identical code rebuilds to the same
|
|
98
|
+
* file (content hash), and without this a revert would import a stale, earlier module instance.
|
|
99
|
+
*/
|
|
100
|
+
importUrl(bundle) {
|
|
101
|
+
return `${bundle.url}?r=${++imports}`;
|
|
102
|
+
},
|
|
103
|
+
build,
|
|
104
|
+
/**
|
|
105
|
+
* Rebuilds (debounced) when anything under the entry's folder changes. `onBuild` gets the bundle
|
|
106
|
+
* or the error. Returns a stop function.
|
|
107
|
+
*/
|
|
108
|
+
watch(onBuild, debounceMs = 50) {
|
|
109
|
+
const dir = dirname(resolve(root, options.entry));
|
|
110
|
+
let timer;
|
|
111
|
+
const watcher = fsWatch(dir, { recursive: true }, () => {
|
|
112
|
+
if (timer) clearTimeout(timer);
|
|
113
|
+
timer = setTimeout(() => {
|
|
114
|
+
timer = void 0;
|
|
115
|
+
build().then(onBuild, (err) => onBuild(err));
|
|
116
|
+
}, debounceMs);
|
|
117
|
+
});
|
|
118
|
+
return () => {
|
|
119
|
+
if (timer) clearTimeout(timer);
|
|
120
|
+
watcher.close();
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
/** Removes bundles other than the latest from the output folder. */
|
|
124
|
+
async prune() {
|
|
125
|
+
const files = await readdir(outDir).catch(() => []);
|
|
126
|
+
await Promise.all(
|
|
127
|
+
files.filter(
|
|
128
|
+
(f) => f.startsWith("main.") && f.endsWith(".mjs") && join(outDir, f) !== last?.file
|
|
129
|
+
).map((f) => rm(join(outDir, f), { force: true }))
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
dispose: () => context.dispose()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
createBundler
|
|
137
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Workers } from '@aethervtt/shard-platform';
|
|
2
|
+
/** What an esbuild metafile says about inputs: each file and what it imports. */
|
|
3
|
+
export interface ModuleGraph {
|
|
4
|
+
inputs: Record<string, {
|
|
5
|
+
imports: readonly {
|
|
6
|
+
path: string;
|
|
7
|
+
external?: boolean;
|
|
8
|
+
}[];
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Each generator's code hash: the hash of the module that defines it and every project module it
|
|
13
|
+
* imports (transitively), from the bundler's module graph. The entry and the module that calls
|
|
14
|
+
* `defineProject` are left out (with what only they import), so editing game systems there never
|
|
15
|
+
* regenerates outputs. Keys are generator names (`<namespace>/<Name>`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function generatorCodeHashes(root: string, namespace: string, entry: string, graph: ModuleGraph): Promise<Record<string, string>>;
|
|
18
|
+
/** The module graph of a project's code (engine packages external), without writing anything. */
|
|
19
|
+
export declare function projectModuleGraph(root: string, entry: string): Promise<ModuleGraph>;
|
|
20
|
+
export interface GeneratorWorkerBundle {
|
|
21
|
+
/** `file://` URL of the module, for `configureProcgenHost({ workerModule })`. */
|
|
22
|
+
url: string;
|
|
23
|
+
file: string;
|
|
24
|
+
hash: string;
|
|
25
|
+
ms: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Bundles the project's code with `@aethervtt/shard-procgen/worker` into one self-contained module (the
|
|
29
|
+
* engine inlined, so a worker thread loads it with no resolver or loader): what generator jobs run
|
|
30
|
+
* on the worker pool. Written to `.shard/build/procgen-worker.<hash>.mjs`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildGeneratorWorker(options: {
|
|
33
|
+
root: string;
|
|
34
|
+
entry: string;
|
|
35
|
+
outDir?: string;
|
|
36
|
+
}): Promise<GeneratorWorkerBundle>;
|
|
37
|
+
export declare function prepareGenerators(options: {
|
|
38
|
+
root: string;
|
|
39
|
+
namespace: string;
|
|
40
|
+
entry: string;
|
|
41
|
+
workers?: Workers;
|
|
42
|
+
graph?: ModuleGraph;
|
|
43
|
+
/** Build the worker bundle (default true); false runs jobs inline. */
|
|
44
|
+
worker?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve once the code hashes are set, building the worker bundle behind (hot reloads): jobs
|
|
47
|
+
* wait for the new bundle, and `worker` in the result is a promise.
|
|
48
|
+
*/
|
|
49
|
+
background?: boolean;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
codeHashes: Record<string, string>;
|
|
52
|
+
worker: GeneratorWorkerBundle | Promise<GeneratorWorkerBundle> | undefined;
|
|
53
|
+
}>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
7
|
+
import { configureProcgenHost, setGeneratorCodeHashes } from "@aethervtt/shard-procgen";
|
|
8
|
+
import * as esbuild from "esbuild";
|
|
9
|
+
const PROJECT_GENERATOR = /\.generator\(\s*['"]([A-Za-z][A-Za-z0-9]*)['"]/g;
|
|
10
|
+
const ENGINE_GENERATOR = /\bdefineGenerator\(\s*['"]([a-z][a-z0-9-]*\/[A-Za-z][A-Za-z0-9]*)['"]/g;
|
|
11
|
+
async function generatorCodeHashes(root, namespace, entry, graph) {
|
|
12
|
+
const scriptsDir = relative(root, dirname(resolve(root, entry))).split("\\").join("/");
|
|
13
|
+
const own = (p) => !p.includes("node_modules/") && !p.startsWith("..") && (scriptsDir === "" || p.startsWith(`${scriptsDir}/`));
|
|
14
|
+
const files = Object.keys(graph.inputs).filter(own);
|
|
15
|
+
const text = /* @__PURE__ */ new Map();
|
|
16
|
+
await Promise.all(files.map(async (f) => text.set(f, await readFile(resolve(root, f), "utf8"))));
|
|
17
|
+
const entryPath = relative(root, resolve(root, entry)).split("\\").join("/");
|
|
18
|
+
const excluded = new Set(
|
|
19
|
+
files.filter((f) => f === entryPath || /\bdefineProject\s*\(/.test(text.get(f)))
|
|
20
|
+
);
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const source = text.get(file);
|
|
24
|
+
const names = [];
|
|
25
|
+
for (const m of source.matchAll(PROJECT_GENERATOR)) names.push(`${namespace}/${m[1]}`);
|
|
26
|
+
for (const m of source.matchAll(ENGINE_GENERATOR)) names.push(m[1]);
|
|
27
|
+
if (names.length === 0) continue;
|
|
28
|
+
const seen = /* @__PURE__ */ new Set([file]);
|
|
29
|
+
const stack = [file];
|
|
30
|
+
while (stack.length > 0) {
|
|
31
|
+
const at = stack.pop();
|
|
32
|
+
for (const imp of graph.inputs[at]?.imports ?? []) {
|
|
33
|
+
if (imp.external || !own(imp.path) || seen.has(imp.path) || excluded.has(imp.path)) continue;
|
|
34
|
+
seen.add(imp.path);
|
|
35
|
+
stack.push(imp.path);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const hash = createHash("sha256");
|
|
39
|
+
for (const f of [...seen].sort()) hash.update(`${f}
|
|
40
|
+
${text.get(f)}
|
|
41
|
+
`);
|
|
42
|
+
const digest = hash.digest("hex").slice(0, 16);
|
|
43
|
+
for (const name of names) out[name] = digest;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
async function projectModuleGraph(root, entry) {
|
|
48
|
+
const result = await esbuild.build({
|
|
49
|
+
absWorkingDir: resolve(root),
|
|
50
|
+
entryPoints: [resolve(root, entry)],
|
|
51
|
+
bundle: true,
|
|
52
|
+
format: "esm",
|
|
53
|
+
platform: "browser",
|
|
54
|
+
external: ["@aethervtt/shard-*", "node:*"],
|
|
55
|
+
write: false,
|
|
56
|
+
metafile: true,
|
|
57
|
+
logLevel: "silent"
|
|
58
|
+
});
|
|
59
|
+
return result.metafile;
|
|
60
|
+
}
|
|
61
|
+
async function buildGeneratorWorker(options) {
|
|
62
|
+
const start = performance.now();
|
|
63
|
+
const root = resolve(options.root);
|
|
64
|
+
const require2 = createRequire(import.meta.url);
|
|
65
|
+
const worker = require2.resolve("@aethervtt/shard-procgen/worker");
|
|
66
|
+
const entry = resolve(root, options.entry);
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
result = await esbuild.build({
|
|
70
|
+
absWorkingDir: root,
|
|
71
|
+
stdin: {
|
|
72
|
+
contents: `import ${JSON.stringify(entry)}
|
|
73
|
+
export { runGeneratorJob, warmUp } from ${JSON.stringify(worker)}
|
|
74
|
+
`,
|
|
75
|
+
resolveDir: root,
|
|
76
|
+
sourcefile: "procgen-worker.ts",
|
|
77
|
+
loader: "ts"
|
|
78
|
+
},
|
|
79
|
+
bundle: true,
|
|
80
|
+
format: "esm",
|
|
81
|
+
platform: "browser",
|
|
82
|
+
target: "es2023",
|
|
83
|
+
// Host-only fallbacks (reading .wasm files from disk) stay dynamic imports that never run here.
|
|
84
|
+
external: ["node:*"],
|
|
85
|
+
// Keep every engine module, ignoring `sideEffects: false`. A worker runs no plugins, so their
|
|
86
|
+
// `provides` can't keep definitions alive, and a job may load any asset type (a noise graph,
|
|
87
|
+
// a mesh, a data asset) whose definition registers on import.
|
|
88
|
+
ignoreAnnotations: true,
|
|
89
|
+
write: false,
|
|
90
|
+
logLevel: "silent"
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const first = err.errors?.[0];
|
|
94
|
+
throw new ShardError(
|
|
95
|
+
"procgen/worker-bundle-failed",
|
|
96
|
+
`Couldn't bundle the generator worker: ${first?.text ?? err.message}`,
|
|
97
|
+
{ hint: "The project code has to bundle for browsers (no Node built-ins).", cause: err }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const code = result.outputFiles[0].contents;
|
|
101
|
+
const hash = createHash("sha256").update(code).digest("hex").slice(0, 16);
|
|
102
|
+
const outDir = resolve(root, options.outDir ?? ".shard/build");
|
|
103
|
+
const file = join(outDir, `procgen-worker.${hash}.mjs`);
|
|
104
|
+
const exists = await access(file).then(
|
|
105
|
+
() => true,
|
|
106
|
+
() => false
|
|
107
|
+
);
|
|
108
|
+
if (!exists) {
|
|
109
|
+
await mkdir(outDir, { recursive: true });
|
|
110
|
+
await writeFile(file, code);
|
|
111
|
+
}
|
|
112
|
+
return { url: pathToFileURL(file).href, file, hash, ms: performance.now() - start };
|
|
113
|
+
}
|
|
114
|
+
let lastWorker;
|
|
115
|
+
async function prepareGenerators(options) {
|
|
116
|
+
const graph = options.graph ?? await projectModuleGraph(options.root, options.entry);
|
|
117
|
+
const codeHashes = await generatorCodeHashes(
|
|
118
|
+
options.root,
|
|
119
|
+
options.namespace,
|
|
120
|
+
options.entry,
|
|
121
|
+
graph
|
|
122
|
+
);
|
|
123
|
+
setGeneratorCodeHashes(codeHashes);
|
|
124
|
+
let worker;
|
|
125
|
+
if (options.worker !== false && options.workers && options.workers.size > 0) {
|
|
126
|
+
const key = `${resolve(options.root)}
|
|
127
|
+
${JSON.stringify(codeHashes)}`;
|
|
128
|
+
if (lastWorker?.key !== key) {
|
|
129
|
+
const previous = lastWorker?.building;
|
|
130
|
+
lastWorker = {
|
|
131
|
+
key,
|
|
132
|
+
building: (previous ?? Promise.resolve()).catch(() => {
|
|
133
|
+
}).then(() => buildGeneratorWorker({ root: options.root, entry: options.entry }))
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const building = lastWorker.building;
|
|
137
|
+
const mine = lastWorker;
|
|
138
|
+
building.catch(() => {
|
|
139
|
+
if (lastWorker === mine) lastWorker = void 0;
|
|
140
|
+
});
|
|
141
|
+
if (options.background) {
|
|
142
|
+
worker = building;
|
|
143
|
+
const url = building.then((b) => b.url);
|
|
144
|
+
url.catch(() => {
|
|
145
|
+
});
|
|
146
|
+
configureProcgenHost({ workers: options.workers, workerModule: url });
|
|
147
|
+
} else {
|
|
148
|
+
worker = await building;
|
|
149
|
+
configureProcgenHost({ workers: options.workers, workerModule: worker.url });
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
configureProcgenHost({ workers: void 0, workerModule: void 0 });
|
|
153
|
+
}
|
|
154
|
+
return { codeHashes, worker };
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
buildGeneratorWorker,
|
|
158
|
+
generatorCodeHashes,
|
|
159
|
+
prepareGenerators,
|
|
160
|
+
projectModuleGraph
|
|
161
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type AssetServer, type ScanReport } from '@aethervtt/shard-assets';
|
|
2
|
+
import { type World } from '@aethervtt/shard-core';
|
|
3
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
4
|
+
import { type Platform } from '@aethervtt/shard-platform';
|
|
5
|
+
import { type ErrorCode, type ManifestValue, ProjectSession } from '@aethervtt/shard-project';
|
|
6
|
+
import { type ProtocolServer } from '@aethervtt/shard-protocol';
|
|
7
|
+
import { OffscreenTarget } from '@aethervtt/shard-render';
|
|
8
|
+
import { type App, type Plugin } from '@aethervtt/shard-runtime';
|
|
9
|
+
import { type LoadedSceneHandle } from '@aethervtt/shard-scene';
|
|
10
|
+
import { type Bundler } from './bundle.js';
|
|
11
|
+
export { createNodePlatform } from '@aethervtt/shard-platform-node';
|
|
12
|
+
export interface OpenProjectOptions {
|
|
13
|
+
/** Project folder (with shard.json). */
|
|
14
|
+
root: string;
|
|
15
|
+
/** Headless render size; defaults to the manifest's window. */
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
/** Load the manifest's start scene (default true). */
|
|
19
|
+
loadStartScene?: boolean;
|
|
20
|
+
/** Share a GPU context across projects (tests); otherwise one is created. */
|
|
21
|
+
gpu?: GpuContext;
|
|
22
|
+
/** Overrides the manifest's `seed`. */
|
|
23
|
+
seed?: number;
|
|
24
|
+
/** Hot reload assets and project code when files change (default false). */
|
|
25
|
+
watch?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 'bundle' (default): esbuild bundles `entry` and hot reload works. 'source': imports `entry`
|
|
28
|
+
* directly (gameplay tests, where test files import the same modules).
|
|
29
|
+
*/
|
|
30
|
+
code?: 'bundle' | 'source';
|
|
31
|
+
/**
|
|
32
|
+
* Run generator jobs on worker threads (default true): the project's code is bundled with the
|
|
33
|
+
* engine into `.shard/build/procgen-worker.<hash>.mjs`. False runs them inline.
|
|
34
|
+
*/
|
|
35
|
+
generatorWorkers?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Where saves and settings go. 'memory' (default): gone when the process exits, so runs, tests,
|
|
38
|
+
* and screenshots never depend on what an earlier session saved. 'files': `.shard/user` in the
|
|
39
|
+
* project, kept across sessions (`shard serve`, `shard mcp`).
|
|
40
|
+
*/
|
|
41
|
+
userData?: 'memory' | 'files';
|
|
42
|
+
}
|
|
43
|
+
export interface HeadlessProject {
|
|
44
|
+
root: string;
|
|
45
|
+
manifest: ManifestValue;
|
|
46
|
+
platform: Platform;
|
|
47
|
+
app: App;
|
|
48
|
+
server: ProtocolServer;
|
|
49
|
+
gpu: GpuContext;
|
|
50
|
+
target: OffscreenTarget;
|
|
51
|
+
scene: LoadedSceneHandle | undefined;
|
|
52
|
+
assets: AssetServer;
|
|
53
|
+
/** What the startup scan imported, skipped, and failed. */
|
|
54
|
+
imports: ScanReport;
|
|
55
|
+
/** The project's code: reloads, status, errors. */
|
|
56
|
+
session: ProjectSession;
|
|
57
|
+
/** The bundler, in 'bundle' mode. */
|
|
58
|
+
bundler: Bundler | undefined;
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
/** Imports the project plugin (default export of `entry`). */
|
|
62
|
+
export declare function importProjectPlugin(root: string, manifest: ManifestValue): Promise<Plugin>;
|
|
63
|
+
/** Opens a project headless: manifest, project code, Dawn GPU, offscreen target, protocol server. */
|
|
64
|
+
export declare function openProject(options: OpenProjectOptions): Promise<HeadlessProject>;
|
|
65
|
+
/** The first stack frame in project source (not node_modules or .shard), as `path:line:col`. */
|
|
66
|
+
export declare function locateInProject(error: unknown, root: string): string | undefined;
|
|
67
|
+
/** Scene files under `scenes/`, as project-relative paths. */
|
|
68
|
+
export declare function listScenes(root: string): Promise<string[]>;
|
|
69
|
+
/**
|
|
70
|
+
* A stable hash of every entity's serializable components (and parents). Equal worlds hash equal,
|
|
71
|
+
* so determinism checks compare one string. `core/Derived` entities (rebuilt from other state, like
|
|
72
|
+
* terrain chunks) are left out, and entity ids are numbered by rank among the rest, so a run that
|
|
73
|
+
* spawns render-only chunks (a GPU) hashes like one that doesn't (headless).
|
|
74
|
+
*/
|
|
75
|
+
export declare function worldHash(world: World): string;
|
|
76
|
+
/**
|
|
77
|
+
* Error codes the engine can raise, scanned from the engine packages' sources (with the hint written
|
|
78
|
+
* next to each). Feeds `.agents/errors.md`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function collectErrorCodes(): Promise<ErrorCode[]>;
|
|
81
|
+
export { type BuiltBundle, type Bundler, createBundler } from './bundle.js';
|
|
82
|
+
export { buildGeneratorWorker, type GeneratorWorkerBundle, generatorCodeHashes, type ModuleGraph, prepareGenerators, projectModuleGraph, } from './generators.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { assetServer } from "@aethervtt/shard-assets";
|
|
7
|
+
import {
|
|
8
|
+
ChildOf,
|
|
9
|
+
Derived,
|
|
10
|
+
ShardError
|
|
11
|
+
} from "@aethervtt/shard-core";
|
|
12
|
+
import { createNodeGpuContext } from "@aethervtt/shard-gpu/node";
|
|
13
|
+
import { createMemoryStorage } from "@aethervtt/shard-platform";
|
|
14
|
+
import { createNodePlatform } from "@aethervtt/shard-platform-node";
|
|
15
|
+
import { configureProcgenHost, procgenHost } from "@aethervtt/shard-procgen";
|
|
16
|
+
import {
|
|
17
|
+
buildApp,
|
|
18
|
+
loadProject,
|
|
19
|
+
loadProjectNavCache,
|
|
20
|
+
loadProjectShaders,
|
|
21
|
+
loadProjectStrings,
|
|
22
|
+
locateInBundle,
|
|
23
|
+
ProjectSession
|
|
24
|
+
} from "@aethervtt/shard-project";
|
|
25
|
+
import { createProtocolServer } from "@aethervtt/shard-protocol";
|
|
26
|
+
import { OffscreenTarget } from "@aethervtt/shard-render";
|
|
27
|
+
import { LogResource } from "@aethervtt/shard-runtime";
|
|
28
|
+
import { loadScene, whenSceneReady } from "@aethervtt/shard-scene";
|
|
29
|
+
import { createBundler } from "./bundle.js";
|
|
30
|
+
import { prepareGenerators } from "./generators.js";
|
|
31
|
+
import { createNodePlatform as createNodePlatform2 } from "@aethervtt/shard-platform-node";
|
|
32
|
+
async function importProjectPlugin(root, manifest) {
|
|
33
|
+
const file = resolve(root, manifest.entry);
|
|
34
|
+
let mod;
|
|
35
|
+
try {
|
|
36
|
+
mod = await import(pathToFileURL(file).href);
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
throw new ShardError(
|
|
39
|
+
"project/entry-failed",
|
|
40
|
+
`Couldn't load ${manifest.entry}: ${cause.message}`,
|
|
41
|
+
{
|
|
42
|
+
path: manifest.entry,
|
|
43
|
+
hint: "The entry must be a module whose default export is defineProject({...}).",
|
|
44
|
+
cause
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const plugin = mod.default;
|
|
49
|
+
if (!plugin || typeof plugin.build !== "function" || typeof plugin.name !== "string") {
|
|
50
|
+
throw new ShardError(
|
|
51
|
+
"project/entry-invalid",
|
|
52
|
+
`${manifest.entry} has no project plugin as its default export`,
|
|
53
|
+
{
|
|
54
|
+
path: manifest.entry,
|
|
55
|
+
hint: "End the file with `export default project`, where project = defineProject({...})."
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return plugin;
|
|
60
|
+
}
|
|
61
|
+
async function openProject(options) {
|
|
62
|
+
const root = resolve(options.root);
|
|
63
|
+
const files = createNodePlatform({ root });
|
|
64
|
+
const platform = options.userData === "files" ? files : { ...files, storage: createMemoryStorage() };
|
|
65
|
+
const loaded = (await loadProject(platform)).manifest;
|
|
66
|
+
const manifest = options.seed === void 0 ? loaded : { ...loaded, seed: options.seed };
|
|
67
|
+
const code = options.code ?? "bundle";
|
|
68
|
+
let bundler;
|
|
69
|
+
let project;
|
|
70
|
+
if (code === "bundle") {
|
|
71
|
+
bundler = await createBundler({ root, entry: manifest.entry });
|
|
72
|
+
let built;
|
|
73
|
+
try {
|
|
74
|
+
built = await bundler.build();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
await bundler.dispose();
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
project = await importBundle(built, manifest);
|
|
80
|
+
} else {
|
|
81
|
+
project = await importProjectPlugin(root, manifest);
|
|
82
|
+
}
|
|
83
|
+
const generators = {
|
|
84
|
+
root,
|
|
85
|
+
namespace: manifest.name,
|
|
86
|
+
entry: manifest.entry,
|
|
87
|
+
workers: platform.workers,
|
|
88
|
+
worker: options.generatorWorkers ?? true
|
|
89
|
+
};
|
|
90
|
+
await prepareGenerators({ ...generators, graph: bundler?.last?.graph });
|
|
91
|
+
const ownGpu = !options.gpu;
|
|
92
|
+
const gpu = options.gpu ?? await createNodeGpuContext({ features: ["timestamp-query"] });
|
|
93
|
+
const target = new OffscreenTarget(gpu, {
|
|
94
|
+
label: "headless",
|
|
95
|
+
width: options.width ?? manifest.window.width,
|
|
96
|
+
height: options.height ?? manifest.window.height
|
|
97
|
+
});
|
|
98
|
+
const app = buildApp({ manifest, project, gpu, target, platform });
|
|
99
|
+
await app.init();
|
|
100
|
+
await loadProjectNavCache(app, platform);
|
|
101
|
+
const stopShaders = await loadProjectShaders(app, platform, { watch: options.watch });
|
|
102
|
+
const assets = assetServer(app.world).configure({ platform, roots: manifest.assetRoots });
|
|
103
|
+
const imports = await assets.scan();
|
|
104
|
+
await loadProjectStrings(app);
|
|
105
|
+
const stopWatching = options.watch ? await assets.watch() : () => {
|
|
106
|
+
};
|
|
107
|
+
let scene;
|
|
108
|
+
if (options.loadStartScene ?? true) {
|
|
109
|
+
const json = JSON.parse(await platform.fs.readText(manifest.startScene));
|
|
110
|
+
scene = loadScene(app.world, json, { id: manifest.startScene });
|
|
111
|
+
await whenSceneReady(app.world, manifest.startScene);
|
|
112
|
+
}
|
|
113
|
+
const log = app.world.resource(LogResource);
|
|
114
|
+
log.annotate = (err) => {
|
|
115
|
+
const last = bundler?.last;
|
|
116
|
+
for (let e = err, depth = 0; e && depth < 5; e = e.cause, depth++) {
|
|
117
|
+
const mapped = last?.map && locateInBundle(
|
|
118
|
+
e,
|
|
119
|
+
last.url,
|
|
120
|
+
last.map,
|
|
121
|
+
(s) => relative(root, resolve(dirname(last.file), s)).split("\\").join("/")
|
|
122
|
+
);
|
|
123
|
+
const source = mapped || locateInProject(e, root);
|
|
124
|
+
if (source) return { source };
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
};
|
|
128
|
+
const session = new ProjectSession(app, {
|
|
129
|
+
namespace: manifest.name,
|
|
130
|
+
current: project,
|
|
131
|
+
...bundler?.last ? { bundle: { hash: bundler.last.hash, ms: bundler.last.ms } } : {}
|
|
132
|
+
});
|
|
133
|
+
if (bundler) {
|
|
134
|
+
const b = bundler;
|
|
135
|
+
session.rebuild = async () => {
|
|
136
|
+
const built = await b.build();
|
|
137
|
+
await prepareGenerators({ ...generators, graph: built.graph, background: true });
|
|
138
|
+
return { load: () => import(b.importUrl(built)), hash: built.hash, ms: built.ms };
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const server = createProtocolServer(app, {
|
|
142
|
+
frames: "manual",
|
|
143
|
+
platform,
|
|
144
|
+
methods: session.methods()
|
|
145
|
+
});
|
|
146
|
+
session.onChange((status) => server.publish("project", status));
|
|
147
|
+
let stopCode = () => {
|
|
148
|
+
};
|
|
149
|
+
if (options.watch && bundler) {
|
|
150
|
+
session.watching = true;
|
|
151
|
+
stopCode = bundler.watch((result) => {
|
|
152
|
+
if (result instanceof Error) session.buildFailed(result);
|
|
153
|
+
else
|
|
154
|
+
void prepareGenerators({ ...generators, graph: result.graph, background: true }).then(
|
|
155
|
+
() => session.reload(() => import(bundler.importUrl(result)), {
|
|
156
|
+
hash: result.hash,
|
|
157
|
+
ms: result.ms
|
|
158
|
+
})
|
|
159
|
+
).catch((err) => session.buildFailed(err));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
root,
|
|
164
|
+
manifest,
|
|
165
|
+
platform,
|
|
166
|
+
app,
|
|
167
|
+
server,
|
|
168
|
+
gpu,
|
|
169
|
+
target,
|
|
170
|
+
scene,
|
|
171
|
+
assets,
|
|
172
|
+
imports,
|
|
173
|
+
session,
|
|
174
|
+
bundler,
|
|
175
|
+
close() {
|
|
176
|
+
stopWatching();
|
|
177
|
+
stopShaders();
|
|
178
|
+
stopCode();
|
|
179
|
+
void bundler?.dispose();
|
|
180
|
+
server.close();
|
|
181
|
+
target.destroy();
|
|
182
|
+
if (ownGpu) gpu.destroy();
|
|
183
|
+
if (procgenHost().workers === platform.workers)
|
|
184
|
+
configureProcgenHost({ workers: void 0, workerModule: void 0 });
|
|
185
|
+
platform.workers?.dispose();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function importBundle(built, manifest) {
|
|
190
|
+
let mod;
|
|
191
|
+
try {
|
|
192
|
+
mod = await import(built.url);
|
|
193
|
+
} catch (cause) {
|
|
194
|
+
const source = built.map ? locateInBundle(cause, built.url, built.map) : void 0;
|
|
195
|
+
throw Object.assign(
|
|
196
|
+
new ShardError(
|
|
197
|
+
"project/entry-failed",
|
|
198
|
+
`Couldn't start ${manifest.entry}: ${cause.message}`,
|
|
199
|
+
{
|
|
200
|
+
path: manifest.entry,
|
|
201
|
+
hint: 'The error is in the project code; see "source" for where.',
|
|
202
|
+
cause
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
{ source }
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const plugin = mod.default;
|
|
209
|
+
if (!plugin || typeof plugin.build !== "function" || typeof plugin.name !== "string") {
|
|
210
|
+
throw new ShardError(
|
|
211
|
+
"project/entry-invalid",
|
|
212
|
+
`${manifest.entry} has no project plugin as its default export`,
|
|
213
|
+
{
|
|
214
|
+
path: manifest.entry,
|
|
215
|
+
hint: "End the file with `export default project`, where project = defineProject({...})."
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return plugin;
|
|
220
|
+
}
|
|
221
|
+
function locateInProject(error, root) {
|
|
222
|
+
const stack = error?.stack;
|
|
223
|
+
if (typeof stack !== "string") return void 0;
|
|
224
|
+
const prefix = resolve(root);
|
|
225
|
+
for (const line of stack.split("\n")) {
|
|
226
|
+
const m = /(?:file:\/\/)?(\/[^\s():]+):(\d+):(\d+)/.exec(line);
|
|
227
|
+
if (!m) continue;
|
|
228
|
+
const file = decodeURIComponent(m[1]);
|
|
229
|
+
if (!file.startsWith(`${prefix}/`)) continue;
|
|
230
|
+
const rel = relative(prefix, file).split("\\").join("/");
|
|
231
|
+
if (rel.startsWith(".shard/") || rel.includes("node_modules/")) continue;
|
|
232
|
+
return `${rel}:${m[2]}:${m[3]}`;
|
|
233
|
+
}
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
async function listScenes(root) {
|
|
237
|
+
const dir = join(resolve(root), "scenes");
|
|
238
|
+
const entries = await readdir(dir, { recursive: true }).catch(() => []);
|
|
239
|
+
return entries.filter((f) => f.endsWith(".scene.json")).map((f) => relative(resolve(root), join(dir, f)).split("\\").join("/")).sort();
|
|
240
|
+
}
|
|
241
|
+
function worldHash(world) {
|
|
242
|
+
const kept = [];
|
|
243
|
+
for (const table of world.allTables()) {
|
|
244
|
+
if (table.has(Derived)) continue;
|
|
245
|
+
for (let row = 0; row < table.count; row++) kept.push(table.entities[row]);
|
|
246
|
+
}
|
|
247
|
+
kept.sort((a, b) => a - b);
|
|
248
|
+
const rank = /* @__PURE__ */ new Map();
|
|
249
|
+
for (let i = 0; i < kept.length; i++) rank.set(kept[i], i);
|
|
250
|
+
const id = (e) => typeof e === "number" && e >= 0 ? rank.get(e) ?? -1 : e;
|
|
251
|
+
const remap = (field, value) => {
|
|
252
|
+
if (value === null || value === void 0) return value;
|
|
253
|
+
if (field.kind === "entity") return id(value);
|
|
254
|
+
if (field.kind === "list" && field.item && Array.isArray(value))
|
|
255
|
+
return value.map((v) => remap(field.item, v));
|
|
256
|
+
if (field.kind === "struct" && field.fields && typeof value === "object")
|
|
257
|
+
return remapFields(field.fields, value);
|
|
258
|
+
return value;
|
|
259
|
+
};
|
|
260
|
+
const remapFields = (fields, value) => {
|
|
261
|
+
const out = { ...value };
|
|
262
|
+
for (const key in fields) if (key in out) out[key] = remap(fields[key], out[key]);
|
|
263
|
+
return out;
|
|
264
|
+
};
|
|
265
|
+
const rows = [];
|
|
266
|
+
for (const table of world.allTables()) {
|
|
267
|
+
if (table.has(Derived)) continue;
|
|
268
|
+
const components = table.components.filter((c) => c.serializable || c === ChildOf);
|
|
269
|
+
for (let row = 0; row < table.count; row++) {
|
|
270
|
+
const entity = table.entities[row];
|
|
271
|
+
const values = components.map((c) => [
|
|
272
|
+
c.name,
|
|
273
|
+
remapFields(c.fields, c.serialize(table.readComponent(c, row)))
|
|
274
|
+
]);
|
|
275
|
+
rows.push(JSON.stringify([rank.get(entity), values]));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return createHash("sha256").update(rows.sort().join("\n")).digest("hex");
|
|
279
|
+
}
|
|
280
|
+
async function collectErrorCodes() {
|
|
281
|
+
const require2 = createRequire(import.meta.url);
|
|
282
|
+
const packagesDir = dirname(dirname(dirname(require2.resolve("@aethervtt/shard-core"))));
|
|
283
|
+
const found = /* @__PURE__ */ new Map();
|
|
284
|
+
const pkgs = await readdir(packagesDir).catch(() => []);
|
|
285
|
+
for (const pkg of pkgs) {
|
|
286
|
+
const src = join(packagesDir, pkg, "src");
|
|
287
|
+
const files = await readdir(src, { recursive: true }).catch(() => []);
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
if (!file.endsWith(".ts") || file.endsWith(".test.ts")) continue;
|
|
290
|
+
const text = await readFile(join(src, file), "utf8");
|
|
291
|
+
const re = /new ShardError\(\s*['"]([a-z0-9-]+\/[a-z0-9-]+)['"]/g;
|
|
292
|
+
for (let m = re.exec(text); m; m = re.exec(text)) {
|
|
293
|
+
const code = m[1];
|
|
294
|
+
const window = text.slice(m.index, m.index + 700);
|
|
295
|
+
const next = window.indexOf("new ShardError", 10);
|
|
296
|
+
const hint = /hint:\s*(['"`])((?:(?!\1).)+)\1/.exec(
|
|
297
|
+
next === -1 ? window : window.slice(0, next)
|
|
298
|
+
)?.[2];
|
|
299
|
+
if (!found.has(code) || !found.get(code).hint && hint) {
|
|
300
|
+
found.set(code, {
|
|
301
|
+
code,
|
|
302
|
+
source: `@aethervtt/shard-${pkg}`,
|
|
303
|
+
...hint && !hint.includes("${") ? { hint } : {}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return [...found.values()];
|
|
310
|
+
}
|
|
311
|
+
import { createBundler as createBundler2 } from "./bundle.js";
|
|
312
|
+
import {
|
|
313
|
+
buildGeneratorWorker,
|
|
314
|
+
generatorCodeHashes,
|
|
315
|
+
prepareGenerators as prepareGenerators2,
|
|
316
|
+
projectModuleGraph
|
|
317
|
+
} from "./generators.js";
|
|
318
|
+
export {
|
|
319
|
+
buildGeneratorWorker,
|
|
320
|
+
collectErrorCodes,
|
|
321
|
+
createBundler2 as createBundler,
|
|
322
|
+
createNodePlatform2 as createNodePlatform,
|
|
323
|
+
generatorCodeHashes,
|
|
324
|
+
importProjectPlugin,
|
|
325
|
+
listScenes,
|
|
326
|
+
locateInProject,
|
|
327
|
+
openProject,
|
|
328
|
+
prepareGenerators2 as prepareGenerators,
|
|
329
|
+
projectModuleGraph,
|
|
330
|
+
worldHash
|
|
331
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aethervtt/shard-node",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@aethervtt/shard-assets": "0.0.1",
|
|
20
|
+
"@aethervtt/shard-core": "0.0.1",
|
|
21
|
+
"@aethervtt/shard-gpu": "0.0.1",
|
|
22
|
+
"@aethervtt/shard-platform": "0.0.1",
|
|
23
|
+
"@aethervtt/shard-platform-node": "0.0.1",
|
|
24
|
+
"@aethervtt/shard-procgen": "0.0.1",
|
|
25
|
+
"@aethervtt/shard-project": "0.0.1",
|
|
26
|
+
"@aethervtt/shard-protocol": "0.0.1",
|
|
27
|
+
"@aethervtt/shard-render": "0.0.1",
|
|
28
|
+
"@aethervtt/shard-runtime": "0.0.1",
|
|
29
|
+
"@aethervtt/shard-scene": "0.0.1",
|
|
30
|
+
"esbuild": "^0.28.2",
|
|
31
|
+
"webgpu": "0.6.1"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/aethervtt/shard.git",
|
|
36
|
+
"directory": "packages/node"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"provenance": true
|
|
41
|
+
},
|
|
42
|
+
"shard": {
|
|
43
|
+
"buildSha": "d6c5c6aef4675575c878b02cf2c8f738b4190229"
|
|
44
|
+
}
|
|
45
|
+
}
|