@forgeax/engine-devkit 0.1.3 → 0.1.6
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/README.md +30 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/cli-output.unit.test.d.ts +2 -0
- package/dist/__tests__/cli-output.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts +2 -0
- package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts.map +1 -0
- package/dist/__tests__/preview-port.integration.test.d.ts +2 -0
- package/dist/__tests__/preview-port.integration.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/sdk-update.unit.test.d.ts +2 -0
- package/dist/__tests__/sdk-update.unit.test.d.ts.map +1 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/bootstrap-commands.d.ts.map +1 -1
- package/dist/cli-output.d.ts +4 -0
- package/dist/cli-output.d.ts.map +1 -0
- package/dist/cli.mjs +728 -641
- package/dist/cli.mjs.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts +2 -0
- package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts.map +1 -0
- package/dist/host/project-bootstrap.d.ts +4 -2
- package/dist/host/project-bootstrap.d.ts.map +1 -1
- package/dist/host/resource-bootstrap.d.ts +5 -3
- package/dist/host/resource-bootstrap.d.ts.map +1 -1
- package/dist/host.d.ts +2 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +662 -655
- package/dist/index.mjs.map +1 -1
- package/dist/init.d.ts +2 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/project.d.ts +0 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/rhi-debug/cli-context.d.ts.map +1 -1
- package/dist/sdk-bootstrap.d.ts +34 -0
- package/dist/sdk-bootstrap.d.ts.map +1 -0
- package/dist/sdk-cli.mjs +412 -66
- package/dist/sdk-cli.mjs.map +1 -1
- package/dist/sdk-install.d.ts.map +1 -1
- package/dist/sdk-update.d.ts +23 -0
- package/dist/sdk-update.d.ts.map +1 -0
- package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts +2 -0
- package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts.map +1 -0
- package/dist/tools/benchmark/harness.d.ts.map +1 -1
- package/dist/tools/browser-host.d.ts.map +1 -1
- package/dist/tools/native-preview.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +29 -30
- package/dist/__tests__/tool-migration.test.d.ts +0 -2
- package/dist/__tests__/tool-migration.test.d.ts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -11,7 +11,10 @@ import { fbxImporter } from '@forgeax/engine-fbx';
|
|
|
11
11
|
import { fontImporter } from '@forgeax/engine-font/font-importer';
|
|
12
12
|
import { gltfImporter } from '@forgeax/engine-gltf';
|
|
13
13
|
import { imageImporter } from '@forgeax/engine-image/image-importer';
|
|
14
|
+
import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
|
|
15
|
+
import { scanInventory } from '@forgeax/engine-pack/scanner';
|
|
14
16
|
import { describeResourcePreviewFailure, validateCanonicalKitReceipt, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution, validatePreviewArtifactManifest } from '@forgeax/engine-preview';
|
|
17
|
+
import { createMaterialPackCooker } from '@forgeax/engine-shader-compiler';
|
|
15
18
|
import { createStandaloneRuntimeAssetBinding, ok, err } from '@forgeax/engine-types';
|
|
16
19
|
import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
|
|
17
20
|
import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
|
|
@@ -21,10 +24,12 @@ import { runCliGltf } from '@forgeax/engine-gltf/cli-gltf';
|
|
|
21
24
|
import { scanEntries } from '@forgeax/engine-pack/cli-asset';
|
|
22
25
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
23
26
|
import { execFile } from 'child_process';
|
|
27
|
+
import { tmpdir } from 'os';
|
|
24
28
|
import { promisify } from 'util';
|
|
25
29
|
import { decodeTape, openReplay, buildFrameModel, createRhiDebugError } from '@forgeax/engine-rhi-debug';
|
|
26
|
-
import {
|
|
27
|
-
import { build,
|
|
30
|
+
import { rhi, createShaderModule } from '@forgeax/engine-rhi-null';
|
|
31
|
+
import { build, preview, createServer as createServer$1 } from 'vite';
|
|
32
|
+
import { createServer } from 'net';
|
|
28
33
|
import { startVitest } from 'vitest/node';
|
|
29
34
|
import materialPreviewPlugin from '@forgeax/engine-preview/material';
|
|
30
35
|
import meshPreviewPlugin from '@forgeax/engine-preview/mesh';
|
|
@@ -296,16 +301,17 @@ var init_dist = __esm({
|
|
|
296
301
|
"src/dist.ts"() {
|
|
297
302
|
}
|
|
298
303
|
});
|
|
304
|
+
|
|
305
|
+
// src/host.ts
|
|
306
|
+
var host_exports = {};
|
|
307
|
+
__export(host_exports, {
|
|
308
|
+
createViteConfig: () => createViteConfig,
|
|
309
|
+
devKitDdcRoots: () => devKitDdcRoots,
|
|
310
|
+
ignoreDevKitCatalogPath: () => ignoreDevKitCatalogPath
|
|
311
|
+
});
|
|
299
312
|
function ignoreDevKitCatalogPath(path) {
|
|
300
313
|
const normalized = path.replace(/\\/g, "/");
|
|
301
|
-
|
|
302
|
-
if (!normalized.endsWith(".meta.json")) return false;
|
|
303
|
-
try {
|
|
304
|
-
const meta = JSON.parse(readFileSync(path, "utf8"));
|
|
305
|
-
return meta.importer === "game-default-target-profile";
|
|
306
|
-
} catch {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
314
|
+
return normalized.split("/").includes("shaders");
|
|
309
315
|
}
|
|
310
316
|
function devKitDdcRoots(projectRoot2) {
|
|
311
317
|
return {
|
|
@@ -319,6 +325,42 @@ function isResourcePreviewIgnoredPath(path) {
|
|
|
319
325
|
function isProjectSourceIgnoredPath(path) {
|
|
320
326
|
return ignoreDevKitCatalogPath(path) || path.endsWith(".wgsl.meta.json");
|
|
321
327
|
}
|
|
328
|
+
async function prepareBuiltinPack(projectRoots, generated, ignorePath) {
|
|
329
|
+
const inventory = await scanInventory(projectRoots, { ignorePath });
|
|
330
|
+
if (!inventory.ok) return void 0;
|
|
331
|
+
const declared = /* @__PURE__ */ new Set();
|
|
332
|
+
for (const declaration of inventory.value.declarations.values()) {
|
|
333
|
+
if (declaration.format === "pack.json") {
|
|
334
|
+
for (const asset of declaration.value.assets) declared.add(asset.guid.toLowerCase());
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
for (const asset of declaration.value.subAssets) declared.add(asset.guid.toLowerCase());
|
|
338
|
+
}
|
|
339
|
+
const missing = BUILTIN_MESH_ASSETS.filter((asset) => !declared.has(asset.guid.toLowerCase()));
|
|
340
|
+
if (missing.length === 0) return void 0;
|
|
341
|
+
const packPath = resolve(generated, "engine-builtins.pack.json");
|
|
342
|
+
await writeFile(
|
|
343
|
+
packPath,
|
|
344
|
+
`${JSON.stringify(
|
|
345
|
+
{
|
|
346
|
+
schemaVersion: "2.0.0",
|
|
347
|
+
kind: "internal-text-package",
|
|
348
|
+
assets: missing.map((asset) => ({
|
|
349
|
+
guid: asset.guid,
|
|
350
|
+
kind: "mesh",
|
|
351
|
+
payload: { geometry: asset.geometry },
|
|
352
|
+
refs: [],
|
|
353
|
+
artifacts: {}
|
|
354
|
+
}))
|
|
355
|
+
},
|
|
356
|
+
null,
|
|
357
|
+
2
|
|
358
|
+
)}
|
|
359
|
+
`,
|
|
360
|
+
"utf8"
|
|
361
|
+
);
|
|
362
|
+
return packPath;
|
|
363
|
+
}
|
|
322
364
|
function findEngineWorkspaceRoot() {
|
|
323
365
|
let cursor = dirname(fileURLToPath(import.meta.url));
|
|
324
366
|
for (; ; ) {
|
|
@@ -334,6 +376,7 @@ async function engineWorkspacePackages() {
|
|
|
334
376
|
const workspaceRoot = findEngineWorkspaceRoot();
|
|
335
377
|
if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
|
|
336
378
|
const packageRoot = resolve(workspaceRoot, "packages");
|
|
379
|
+
if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
|
|
337
380
|
const packages = /* @__PURE__ */ new Map();
|
|
338
381
|
for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
|
|
339
382
|
if (!entry.isDirectory()) continue;
|
|
@@ -428,6 +471,18 @@ async function createEngineWorkspaceResolver() {
|
|
|
428
471
|
}
|
|
429
472
|
};
|
|
430
473
|
}
|
|
474
|
+
async function consumerEngineAliases(projectRoot2) {
|
|
475
|
+
const root = resolve(projectRoot2, "node_modules", ".pnpm", "node_modules", "@forgeax");
|
|
476
|
+
try {
|
|
477
|
+
const entries2 = await readdir(root, { withFileTypes: true });
|
|
478
|
+
return entries2.filter((entry) => entry.isDirectory() && entry.name.startsWith("engine-")).map((entry) => ({
|
|
479
|
+
find: `@forgeax/${entry.name}`,
|
|
480
|
+
replacement: resolve(root, entry.name)
|
|
481
|
+
})).sort((a, b) => a.find.localeCompare(b.find));
|
|
482
|
+
} catch {
|
|
483
|
+
return [];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
431
486
|
async function resolveCanonicalKit() {
|
|
432
487
|
const packageJson = hostRequire.resolve("@forgeax/engine-preview/package.json");
|
|
433
488
|
const root = resolve(packageJson, "..", "assets/canonical-kit");
|
|
@@ -502,6 +557,7 @@ function hostSource(facts, bootstrapRoot, canonicalEnvironmentGuid) {
|
|
|
502
557
|
const plugins = bootstrapRoot === "resource-bootstrap" ? [] : [
|
|
503
558
|
`webAudioPlugin()`,
|
|
504
559
|
`audioPlugin()`,
|
|
560
|
+
`skinningPlugin()`,
|
|
505
561
|
...facts.physics === void 0 ? [] : [`physicsPlugin('${facts.physics === "2d" ? "rapier-2d" : "rapier-3d"}')`]
|
|
506
562
|
];
|
|
507
563
|
const bootstrapPlugin = facts.bootstrapEntry === void 0 ? "" : `const bootstrapModule = await import(${JSON.stringify(
|
|
@@ -519,20 +575,24 @@ await app.pluginContext.plugin({
|
|
|
519
575
|
},
|
|
520
576
|
});`;
|
|
521
577
|
return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
|
|
522
|
-
import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine
|
|
523
|
-
import { createCatalogSource } from '@forgeax/engine
|
|
524
|
-
import {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
import {
|
|
529
|
-
import {
|
|
530
|
-
import {
|
|
531
|
-
import {
|
|
532
|
-
import {
|
|
533
|
-
import {
|
|
534
|
-
import {
|
|
535
|
-
import {
|
|
578
|
+
import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
|
|
579
|
+
import { createCatalogSource } from '@forgeax/engine/assets-runtime';
|
|
580
|
+
import {
|
|
581
|
+
createRuntimeAssetImportTransport,
|
|
582
|
+
runtimeBinding,
|
|
583
|
+
} from 'virtual:forgeax/pack-runtime';
|
|
584
|
+
import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
|
|
585
|
+
import { audioPlugin } from '@forgeax/engine/audio';
|
|
586
|
+
import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
|
|
587
|
+
import { physicsPlugin } from '@forgeax/engine/physics';
|
|
588
|
+
import { skinningPlugin } from '@forgeax/engine/skinning';
|
|
589
|
+
import { createPrimitiveMesh } from '@forgeax/engine/geometry';
|
|
590
|
+
import { mat4 } from '@forgeax/engine/math';
|
|
591
|
+
import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
|
|
592
|
+
import { Transform } from '@forgeax/engine/scene';
|
|
593
|
+
import { type SceneAsset } from '@forgeax/engine/types';
|
|
594
|
+
import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
|
|
595
|
+
import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
|
|
536
596
|
|
|
537
597
|
const pluginCatalog = new Map([
|
|
538
598
|
${catalogModules(facts).map(
|
|
@@ -631,16 +691,39 @@ const resizeCanvas = () => {
|
|
|
631
691
|
const resizeObserver = new ResizeObserver(resizeCanvas);
|
|
632
692
|
resizeObserver.observe(canvas);
|
|
633
693
|
resizeCanvas();
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
:
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
694
|
+
const runtimeScopeBinding = runtimeBinding;
|
|
695
|
+
if (runtimeScopeBinding === undefined) {
|
|
696
|
+
throw new Error('forgeax: Vite Pack runtime binding is required in the generated host');
|
|
697
|
+
}
|
|
698
|
+
const assetCatalog = createCatalogSource({
|
|
699
|
+
url: import.meta.env.DEV
|
|
700
|
+
? runtimeScopeBinding.catalogUrl
|
|
701
|
+
: new URL('pack-index.json', document.baseURI).href,
|
|
702
|
+
...(import.meta.env.DEV ? { expectedScope: runtimeScopeBinding } : {}),
|
|
703
|
+
});
|
|
640
704
|
const bundler = {
|
|
641
705
|
...forgeaxBundlerAdapter(),
|
|
642
|
-
...(
|
|
706
|
+
...(import.meta.env.DEV
|
|
707
|
+
? { importTransport: createRuntimeAssetImportTransport(runtimeScopeBinding) }
|
|
708
|
+
: {}),
|
|
643
709
|
};
|
|
710
|
+
const assetPreparation = new WeakMap();
|
|
711
|
+
|
|
712
|
+
function prepareAssetRegistry(assets) {
|
|
713
|
+
const existing = assetPreparation.get(assets);
|
|
714
|
+
if (existing !== undefined) return existing;
|
|
715
|
+
const pending = (async () => {
|
|
716
|
+
assets.configureRuntimeBinding(runtimeScopeBinding);
|
|
717
|
+
assets.setCatalogSource(assetCatalog);
|
|
718
|
+
if (vfxRuntimeHost !== undefined) {
|
|
719
|
+
assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
|
|
720
|
+
}
|
|
721
|
+
const catalog = await assets.enumerateCatalog();
|
|
722
|
+
if (!catalog.ok) throw catalog.error;
|
|
723
|
+
})();
|
|
724
|
+
assetPreparation.set(assets, pending);
|
|
725
|
+
return pending;
|
|
726
|
+
}
|
|
644
727
|
|
|
645
728
|
function previewStable(value) {
|
|
646
729
|
if (value instanceof ArrayBuffer) return 'ArrayBuffer:' + JSON.stringify(Array.from(new Uint8Array(value)));
|
|
@@ -701,13 +784,14 @@ async function previewOwnerFacts(assets, resource, payload) {
|
|
|
701
784
|
}
|
|
702
785
|
|
|
703
786
|
async function prepareProject(app) {
|
|
787
|
+
await prepareAssetRegistry(app.assets);
|
|
704
788
|
previewWorld = app.world;
|
|
705
789
|
const assets = app.assets;
|
|
706
790
|
let canonicalEnvironment;
|
|
707
791
|
if (resource !== undefined) {
|
|
708
792
|
if (resource.kind !== 'texture') {
|
|
709
793
|
if (canonicalEnvironmentGuid === null) throw new Error('canonical preview environment is unavailable');
|
|
710
|
-
const environment = await assets.
|
|
794
|
+
const environment = await assets.loadByGuid(assets.parseGuid(canonicalEnvironmentGuid));
|
|
711
795
|
if (!environment.ok) throw environment.error;
|
|
712
796
|
if (environment.value.kind !== 'equirect') {
|
|
713
797
|
throw new Error(\`canonical preview environment expected equirect, received \${environment.value.kind}\`);
|
|
@@ -722,7 +806,7 @@ if (resource !== undefined) {
|
|
|
722
806
|
const attached = await vfxRuntimeHost.attachWorld({ world: app.world, assets });
|
|
723
807
|
if (!attached.ok) throw attached.error;
|
|
724
808
|
}
|
|
725
|
-
const loaded = await assets.
|
|
809
|
+
const loaded = await assets.loadByGuid(assets.parseGuid(resource.guid));
|
|
726
810
|
if (!loaded.ok) throw loaded.error;
|
|
727
811
|
const payload = loaded.value;
|
|
728
812
|
if (resource.kind === 'vfx' && payload.kind !== 'particle-effect') {
|
|
@@ -735,7 +819,7 @@ if (resource !== undefined) {
|
|
|
735
819
|
const meshMaterialHandles = resource.kind === 'mesh' && payload.kind === 'mesh'
|
|
736
820
|
? await Promise.all(payload.materialSlots.map(async (slot) => {
|
|
737
821
|
if (slot.defaultMaterial === undefined) return 0;
|
|
738
|
-
const material = await assets.
|
|
822
|
+
const material = await assets.loadByGuid(assets.parseGuid(slot.defaultMaterial));
|
|
739
823
|
if (!material.ok) throw material.error;
|
|
740
824
|
if (material.value.kind !== 'material') {
|
|
741
825
|
throw new Error('mesh material slot resolved to a non-material asset');
|
|
@@ -865,15 +949,13 @@ let defaultScene;
|
|
|
865
949
|
let defaultSceneRoot;
|
|
866
950
|
const defaultSceneGuid = ${JSON.stringify(facts.defaultScene)};
|
|
867
951
|
if (defaultSceneGuid !== undefined) {
|
|
868
|
-
const loaded = await assets.
|
|
952
|
+
const loaded = await assets.loadByGuid<SceneAsset>(assets.parseGuid(defaultSceneGuid));
|
|
869
953
|
if (!loaded.ok) throw loaded.error;
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const handle = app.world.allocSharedRef('SceneAsset', projected.value);
|
|
874
|
-
const instantiated = worldInstantiateScene(app.world, handle);
|
|
954
|
+
defaultScene = loaded.value;
|
|
955
|
+
const handle = app.world.allocSharedRef('SceneAsset', loaded.value);
|
|
956
|
+
const instantiated = assets.instantiate<SceneAsset>(handle, app.world);
|
|
875
957
|
if (!instantiated.ok) throw instantiated.error;
|
|
876
|
-
defaultSceneRoot = instantiated.value
|
|
958
|
+
defaultSceneRoot = instantiated.value;
|
|
877
959
|
}
|
|
878
960
|
const uiRoot = document.querySelector('#game-ui');
|
|
879
961
|
await app.pluginContext.plugin(gameHostPlugin({
|
|
@@ -912,8 +994,6 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
912
994
|
...(resource === undefined ? {} : { resource }),
|
|
913
995
|
canvas,
|
|
914
996
|
app: {
|
|
915
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
916
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
917
997
|
plugins: [${plugins.join(", ")}],
|
|
918
998
|
...(vfxRuntimeHost === undefined ? {} : { features: [vfxRuntimeHost.feature] }),
|
|
919
999
|
},
|
|
@@ -1018,12 +1098,16 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
1018
1098
|
window.addEventListener('pagehide', () => void host.value.dispose(), { once: true });
|
|
1019
1099
|
}
|
|
1020
1100
|
} else {
|
|
1101
|
+
// WebDriver/headless browsers cannot satisfy the trusted-gesture requirement
|
|
1102
|
+
// for pointer lock. Keep the game running and let its keyboard fallback own
|
|
1103
|
+
// camera input; real browser users retain the normal click-to-lock path.
|
|
1104
|
+
const pointerLockAllowed =
|
|
1105
|
+
typeof navigator !== 'undefined' && navigator.webdriver === true ? () => false : undefined;
|
|
1021
1106
|
const result = await createApp(
|
|
1022
1107
|
canvas,
|
|
1023
1108
|
{
|
|
1024
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
1025
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
1026
1109
|
plugins: [${plugins.join(", ")}],
|
|
1110
|
+
...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
|
|
1027
1111
|
},
|
|
1028
1112
|
bundler,
|
|
1029
1113
|
);
|
|
@@ -1082,10 +1166,32 @@ function htmlSource(title) {
|
|
|
1082
1166
|
<div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
|
|
1083
1167
|
<script>
|
|
1084
1168
|
(() => {
|
|
1169
|
+
const formatStartupFailure = (reason) => {
|
|
1170
|
+
if (reason instanceof Error) return reason.message;
|
|
1171
|
+
if (reason !== null && typeof reason === 'object') {
|
|
1172
|
+
const record = reason;
|
|
1173
|
+
const lines = [];
|
|
1174
|
+
for (const key of ['code', 'expected', 'hint']) {
|
|
1175
|
+
if (typeof record[key] === 'string' && record[key].length > 0) {
|
|
1176
|
+
lines.push(key + ': ' + record[key]);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const detail = record.detail;
|
|
1180
|
+
if (detail !== null && typeof detail === 'object') {
|
|
1181
|
+
for (const key of ['reason', 'guid']) {
|
|
1182
|
+
if (typeof detail[key] === 'string' && detail[key].length > 0) {
|
|
1183
|
+
lines.push('detail.' + key + ': ' + detail[key]);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (lines.length > 0) return lines.join('\\n');
|
|
1188
|
+
}
|
|
1189
|
+
return String(reason ?? 'Unknown startup failure');
|
|
1190
|
+
};
|
|
1085
1191
|
const show = (reason) => {
|
|
1086
1192
|
const notice = document.querySelector('#forgeax-fatal');
|
|
1087
1193
|
if (!(notice instanceof HTMLElement)) return;
|
|
1088
|
-
const message =
|
|
1194
|
+
const message = formatStartupFailure(reason);
|
|
1089
1195
|
notice.textContent = 'ForgeaX game failed to start.\\n' + message;
|
|
1090
1196
|
notice.style.display = 'grid';
|
|
1091
1197
|
};
|
|
@@ -1100,29 +1206,32 @@ function htmlSource(title) {
|
|
|
1100
1206
|
}
|
|
1101
1207
|
async function createViteConfig(facts, command2, base = "/", options = {}) {
|
|
1102
1208
|
const bootstrapRoot = options.bootstrapRoot ?? "project-bootstrap";
|
|
1103
|
-
const canonicalKit =
|
|
1209
|
+
const canonicalKit = await resolveCanonicalKit();
|
|
1104
1210
|
const generated = resolve(facts.root, ".forgeax", "generated");
|
|
1105
1211
|
await mkdir(generated, { recursive: true });
|
|
1212
|
+
const projectRoots = facts.assetRoots.map((root) => resolve(facts.root, root));
|
|
1213
|
+
const ignorePath = bootstrapRoot === "resource-bootstrap" ? isResourcePreviewIgnoredPath : isProjectSourceIgnoredPath;
|
|
1214
|
+
const builtinPack = await prepareBuiltinPack(projectRoots, generated, ignorePath);
|
|
1106
1215
|
await Promise.all([
|
|
1107
1216
|
writeFile(resolve(generated, "index.html"), htmlSource(facts.name)),
|
|
1108
1217
|
writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
|
|
1109
1218
|
]);
|
|
1110
1219
|
const roots = [
|
|
1111
|
-
...
|
|
1220
|
+
...projectRoots,
|
|
1221
|
+
...builtinPack === void 0 ? [] : [builtinPack],
|
|
1112
1222
|
...canonicalKit === void 0 ? [] : [canonicalKit.root]
|
|
1113
1223
|
];
|
|
1114
1224
|
const importers = [...projectImporters(facts)];
|
|
1115
1225
|
const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
|
|
1116
1226
|
const engineWorkspaceResolver = await createEngineWorkspaceResolver();
|
|
1227
|
+
const consumerAliases = await consumerEngineAliases(facts.root);
|
|
1117
1228
|
const plugins = [
|
|
1118
1229
|
...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
|
|
1119
1230
|
forgeaxShader(),
|
|
1120
1231
|
pluginPack({
|
|
1121
1232
|
roots,
|
|
1122
|
-
base,
|
|
1123
1233
|
runtimeBinding,
|
|
1124
1234
|
ddc: devKitDdcRoots(facts.root),
|
|
1125
|
-
scriptablePack: {},
|
|
1126
1235
|
refresh: command2 === "serve" ? reloadAssetHost() : void 0,
|
|
1127
1236
|
importers: [
|
|
1128
1237
|
audioImporter,
|
|
@@ -1132,8 +1241,8 @@ async function createViteConfig(facts, command2, base = "/", options = {}) {
|
|
|
1132
1241
|
fontImporter,
|
|
1133
1242
|
...importers
|
|
1134
1243
|
],
|
|
1135
|
-
cookers: [createParticleCodeNativeCookerFromRoots(roots)],
|
|
1136
|
-
|
|
1244
|
+
cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
|
|
1245
|
+
ignorePath
|
|
1137
1246
|
})
|
|
1138
1247
|
];
|
|
1139
1248
|
return {
|
|
@@ -1142,8 +1251,11 @@ async function createViteConfig(facts, command2, base = "/", options = {}) {
|
|
|
1142
1251
|
configFile: false,
|
|
1143
1252
|
publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
|
|
1144
1253
|
plugins,
|
|
1145
|
-
resolve: {
|
|
1146
|
-
|
|
1254
|
+
resolve: {
|
|
1255
|
+
alias: consumerAliases,
|
|
1256
|
+
dedupe: ["@forgeax/engine"]
|
|
1257
|
+
},
|
|
1258
|
+
server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
|
|
1147
1259
|
build: {
|
|
1148
1260
|
target: "esnext",
|
|
1149
1261
|
outDir: options.outDir ?? resolve(facts.root, "dist"),
|
|
@@ -1167,7 +1279,7 @@ async function readJson(path) {
|
|
|
1167
1279
|
function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
|
|
1168
1280
|
for (const entry of entries2) {
|
|
1169
1281
|
const realm = entry.realm ?? inheritedRealm ?? "engine";
|
|
1170
|
-
if (realm
|
|
1282
|
+
if (realm !== "engine") return { id: entry.id, realm };
|
|
1171
1283
|
if (entry.group === true) {
|
|
1172
1284
|
const unsupported = firstUnsupportedStandaloneRealm(
|
|
1173
1285
|
entry.config,
|
|
@@ -1285,13 +1397,10 @@ async function readProjectFacts(rootInput = process.cwd()) {
|
|
|
1285
1397
|
}
|
|
1286
1398
|
};
|
|
1287
1399
|
}
|
|
1288
|
-
function isCommandError(value) {
|
|
1289
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1290
|
-
const candidate = value;
|
|
1291
|
-
return typeof candidate.code === "string" && typeof candidate.expected === "string" && typeof candidate.hint === "string" && candidate.detail !== null && typeof candidate.detail === "object" && !Array.isArray(candidate.detail);
|
|
1292
|
-
}
|
|
1293
1400
|
function commandError(cause, fallbackCode) {
|
|
1294
|
-
if (
|
|
1401
|
+
if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
|
|
1402
|
+
return cause;
|
|
1403
|
+
}
|
|
1295
1404
|
return {
|
|
1296
1405
|
code: fallbackCode,
|
|
1297
1406
|
expected: "the ForgeaX command to complete",
|
|
@@ -1303,16 +1412,33 @@ var init_project = __esm({
|
|
|
1303
1412
|
"src/project.ts"() {
|
|
1304
1413
|
}
|
|
1305
1414
|
});
|
|
1306
|
-
|
|
1307
|
-
|
|
1415
|
+
|
|
1416
|
+
// src/types.ts
|
|
1417
|
+
function parseProjectPortOption(value, provided) {
|
|
1418
|
+
if (!provided) return { ok: true, value: void 0 };
|
|
1419
|
+
if (value !== void 0 && /^(0|[1-9]\d*)$/.test(value)) {
|
|
1420
|
+
const port = Number(value);
|
|
1421
|
+
if (Number.isSafeInteger(port) && port <= 65535) return { ok: true, value: port };
|
|
1422
|
+
}
|
|
1423
|
+
return {
|
|
1424
|
+
ok: false,
|
|
1425
|
+
error: {
|
|
1426
|
+
code: "cli-parse-error",
|
|
1427
|
+
expected: "--port to be 0 or an integer from 1 to 65535",
|
|
1428
|
+
hint: "Omit --port for strict 5173, pass a positive port for a strict binding, or pass 0 for an OS-assigned port.",
|
|
1429
|
+
detail: { option: "--port", received: value ?? null }
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1308
1432
|
}
|
|
1309
|
-
function
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1433
|
+
function resolveProjectPort(port) {
|
|
1434
|
+
return { port: port ?? 5173, strictPort: port !== 0 };
|
|
1435
|
+
}
|
|
1436
|
+
var init_types = __esm({
|
|
1437
|
+
"src/types.ts"() {
|
|
1314
1438
|
}
|
|
1315
|
-
|
|
1439
|
+
});
|
|
1440
|
+
function failure(error) {
|
|
1441
|
+
return { ok: false, error };
|
|
1316
1442
|
}
|
|
1317
1443
|
async function sourcesAt(path) {
|
|
1318
1444
|
const info = await stat(path);
|
|
@@ -1397,15 +1523,16 @@ async function addGltf(sourcePath, dryRun2) {
|
|
|
1397
1523
|
stderrWrite: (line) => stderr.push(line)
|
|
1398
1524
|
});
|
|
1399
1525
|
if (exitCode !== 0) {
|
|
1400
|
-
|
|
1401
|
-
stderr
|
|
1402
|
-
|
|
1526
|
+
try {
|
|
1527
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
1528
|
+
} catch {
|
|
1529
|
+
return failure({
|
|
1403
1530
|
code: "asset-add-failed",
|
|
1404
1531
|
expected: "the glTF producer to create or reuse a valid sidecar",
|
|
1405
|
-
hint: "Inspect the glTF source and its external references."
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1532
|
+
hint: "Inspect the glTF source and its external references.",
|
|
1533
|
+
detail: { source: sourcePath, diagnostic: stderr.join("\n") }
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1409
1536
|
}
|
|
1410
1537
|
return { ok: true, value: { source: sourcePath, metaPath: `${sourcePath}.meta.json` } };
|
|
1411
1538
|
}
|
|
@@ -1448,15 +1575,16 @@ async function entries(options) {
|
|
|
1448
1575
|
{ stdoutWrite: (line) => stdout.push(line), stderrWrite: (line) => stderr.push(line) }
|
|
1449
1576
|
);
|
|
1450
1577
|
if (!result.ok) {
|
|
1451
|
-
|
|
1452
|
-
stderr
|
|
1453
|
-
|
|
1578
|
+
try {
|
|
1579
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
1580
|
+
} catch {
|
|
1581
|
+
return failure({
|
|
1454
1582
|
code: "asset-authority-invalid",
|
|
1455
1583
|
expected: "all asset roots and sidecars to pass the pack scanner",
|
|
1456
|
-
hint: "Repair the first invalid asset authority reported by the scanner."
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1584
|
+
hint: "Repair the first invalid asset authority reported by the scanner.",
|
|
1585
|
+
detail: { diagnostic: stderr.join("\n") }
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1460
1588
|
}
|
|
1461
1589
|
return { ok: true, value: { facts: facts.value, entries: result.value } };
|
|
1462
1590
|
}
|
|
@@ -1541,7 +1669,6 @@ var init_package = __esm({
|
|
|
1541
1669
|
"@forgeax/engine-audio-webaudio": "workspace:*",
|
|
1542
1670
|
"@forgeax/engine-fbx": "workspace:*",
|
|
1543
1671
|
"@forgeax/engine-font": "workspace:*",
|
|
1544
|
-
"@forgeax/engine-geometry": "workspace:*",
|
|
1545
1672
|
"@forgeax/engine-gltf": "workspace:*",
|
|
1546
1673
|
"@forgeax/engine-image": "workspace:*",
|
|
1547
1674
|
"@forgeax/engine-input": "workspace:*",
|
|
@@ -1551,6 +1678,7 @@ var init_package = __esm({
|
|
|
1551
1678
|
"@forgeax/engine-preview": "workspace:*",
|
|
1552
1679
|
"@forgeax/engine-project": "workspace:*",
|
|
1553
1680
|
"@forgeax/engine-rhi-debug": "workspace:*",
|
|
1681
|
+
"@forgeax/engine-rhi-null": "workspace:*",
|
|
1554
1682
|
"@forgeax/engine-render": "workspace:*",
|
|
1555
1683
|
"@forgeax/engine-render-graph": "workspace:*",
|
|
1556
1684
|
"@forgeax/engine-runtime": "workspace:*",
|
|
@@ -1568,7 +1696,6 @@ var init_package = __esm({
|
|
|
1568
1696
|
vitest: "4.1.5"
|
|
1569
1697
|
},
|
|
1570
1698
|
devDependencies: {
|
|
1571
|
-
"@forgeax/engine-rhi-null": "workspace:*",
|
|
1572
1699
|
"@types/node": "^20.14.0"
|
|
1573
1700
|
},
|
|
1574
1701
|
forgeax: {
|
|
@@ -1694,6 +1821,7 @@ function createInitPlan(facts, sdk) {
|
|
|
1694
1821
|
value: {
|
|
1695
1822
|
root: facts.root,
|
|
1696
1823
|
version: sdk?.sdkVersion ?? devkitVersion,
|
|
1824
|
+
...sdk === void 0 ? {} : { pnpmVersion: sdk.requirements.pnpm },
|
|
1697
1825
|
archiveBacked: sdk !== void 0,
|
|
1698
1826
|
dependencyChanges,
|
|
1699
1827
|
scriptChanges
|
|
@@ -1714,23 +1842,8 @@ async function applyInitPlan(facts, plan, options) {
|
|
|
1714
1842
|
for (const change of plan.scriptChanges) scripts[change.name] = change.to;
|
|
1715
1843
|
manifest.scripts = scripts;
|
|
1716
1844
|
if (plan.archiveBacked) {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
const pnpm = pnpmValue !== null && typeof pnpmValue === "object" ? pnpmValue : {};
|
|
1720
|
-
manifest.pnpm = {
|
|
1721
|
-
...pnpm,
|
|
1722
|
-
onlyBuiltDependencies: [
|
|
1723
|
-
"@forgeax/engine-codec",
|
|
1724
|
-
"@forgeax/engine-fbx",
|
|
1725
|
-
"@forgeax/engine-wgpu-wasm",
|
|
1726
|
-
"esbuild"
|
|
1727
|
-
],
|
|
1728
|
-
supportedArchitectures: {
|
|
1729
|
-
os: ["darwin", "linux", "win32"],
|
|
1730
|
-
cpu: ["x64", "arm64"],
|
|
1731
|
-
libc: ["glibc", "musl"]
|
|
1732
|
-
}
|
|
1733
|
-
};
|
|
1845
|
+
if (plan.pnpmVersion === void 0) throw new Error("sdk-pnpm-version-missing");
|
|
1846
|
+
manifest.packageManager = `pnpm@${plan.pnpmVersion}`;
|
|
1734
1847
|
}
|
|
1735
1848
|
await writeFile(resolve(facts.root, "package.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1736
1849
|
`);
|
|
@@ -1796,6 +1909,270 @@ var init_sdk = __esm({
|
|
|
1796
1909
|
"src/sdk.ts"() {
|
|
1797
1910
|
}
|
|
1798
1911
|
});
|
|
1912
|
+
function agentOnboarding(sdk, projectRoot2) {
|
|
1913
|
+
const root = projectRoot2 ?? sdk.root;
|
|
1914
|
+
return {
|
|
1915
|
+
read: [
|
|
1916
|
+
resolve(root, "AGENTS.md"),
|
|
1917
|
+
resolve(root, "skills", "forgeax-engine-sdk", "SKILL.md"),
|
|
1918
|
+
resolve(root, "skills", "forgeax-engine-sdk", "references", "feature-catalog.md")
|
|
1919
|
+
],
|
|
1920
|
+
next: projectRoot2 === void 0 ? {
|
|
1921
|
+
cwd: root,
|
|
1922
|
+
argv: ["node", "./bin/forgeax.mjs", "new", "../my-game"]
|
|
1923
|
+
} : { cwd: root, argv: ["pnpm", "exec", "forgeax", "list", "--json"] }
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function sdkInitPath(sdk) {
|
|
1927
|
+
return resolve(sdk.root, ...SDK_INIT_PATH);
|
|
1928
|
+
}
|
|
1929
|
+
function commandFailure(code, expected, hint, detail = {}) {
|
|
1930
|
+
return { ok: false, error: { code, expected, hint, detail } };
|
|
1931
|
+
}
|
|
1932
|
+
function sdkProjectInstallArgs(store) {
|
|
1933
|
+
const common = [
|
|
1934
|
+
"install",
|
|
1935
|
+
"--frozen-lockfile",
|
|
1936
|
+
"--ignore-scripts",
|
|
1937
|
+
"--side-effects-cache=true",
|
|
1938
|
+
"--child-concurrency=1"
|
|
1939
|
+
];
|
|
1940
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1941
|
+
}
|
|
1942
|
+
function sdkBootstrapInstallArgs(store) {
|
|
1943
|
+
const common = [
|
|
1944
|
+
"install",
|
|
1945
|
+
"--frozen-lockfile",
|
|
1946
|
+
"--child-concurrency=1",
|
|
1947
|
+
"--side-effects-cache=true"
|
|
1948
|
+
];
|
|
1949
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1950
|
+
}
|
|
1951
|
+
function supportedPnpm(version) {
|
|
1952
|
+
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
1953
|
+
return major === 11 && minor >= 7;
|
|
1954
|
+
}
|
|
1955
|
+
async function currentPnpm() {
|
|
1956
|
+
try {
|
|
1957
|
+
const result = await execFileAsync("pnpm", ["--version"], { maxBuffer: 1024 * 1024 });
|
|
1958
|
+
const version = result.stdout.trim();
|
|
1959
|
+
if (version.length === 0) throw new Error("pnpm returned an empty version");
|
|
1960
|
+
return { ok: true, value: version };
|
|
1961
|
+
} catch (cause) {
|
|
1962
|
+
return commandFailure(
|
|
1963
|
+
"pnpm-unavailable",
|
|
1964
|
+
"pnpm 11.7.0 or newer in the pnpm 11 line to be available on PATH",
|
|
1965
|
+
"Install or activate pnpm 11, then rerun forgeax init from the SDK root.",
|
|
1966
|
+
{ reason: cause instanceof Error ? cause.message : String(cause) }
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
function matchesState(sdk, state) {
|
|
1971
|
+
if (state === null || typeof state !== "object" || Array.isArray(state)) return false;
|
|
1972
|
+
const value = state;
|
|
1973
|
+
return value.schemaVersion === SDK_INIT_SCHEMA_VERSION && value.sdkVersion === sdk.manifest.sdkVersion && value.engineCommit === sdk.manifest.engineCommit && typeof value.pnpm === "string" && supportedPnpm(value.pnpm) && value.node === process.versions.node && value.platform === process.platform && value.arch === process.arch;
|
|
1974
|
+
}
|
|
1975
|
+
async function readSdkInitState(sdk) {
|
|
1976
|
+
try {
|
|
1977
|
+
const value = JSON.parse(await readFile(sdkInitPath(sdk), "utf8"));
|
|
1978
|
+
return matchesState(sdk, value) ? value : void 0;
|
|
1979
|
+
} catch {
|
|
1980
|
+
return void 0;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
async function requireSdkInitialization(sdk) {
|
|
1984
|
+
const state = await readSdkInitState(sdk);
|
|
1985
|
+
if (state === void 0) {
|
|
1986
|
+
return commandFailure(
|
|
1987
|
+
"sdk-not-initialized",
|
|
1988
|
+
"the downloaded SDK to be initialized for this Node/pnpm/platform tuple",
|
|
1989
|
+
"Run ./bin/forgeax init from the SDK root once, then run forgeax new outside the SDK.",
|
|
1990
|
+
{
|
|
1991
|
+
sdkRoot: sdk.root,
|
|
1992
|
+
sdkVersion: sdk.manifest.sdkVersion,
|
|
1993
|
+
pnpm: sdk.manifest.requirements.pnpm,
|
|
1994
|
+
node: process.versions.node,
|
|
1995
|
+
platform: process.platform,
|
|
1996
|
+
arch: process.arch
|
|
1997
|
+
}
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
return { ok: true, value: state };
|
|
2001
|
+
}
|
|
2002
|
+
async function copyBootstrapInputs(sdk, root) {
|
|
2003
|
+
const template = sdk.templates.get(sdk.defaultTemplate);
|
|
2004
|
+
if (template === void 0) throw new Error("sdk-default-template-missing");
|
|
2005
|
+
for (const name of ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"]) {
|
|
2006
|
+
await cp(resolve(template, name), resolve(root, name));
|
|
2007
|
+
}
|
|
2008
|
+
try {
|
|
2009
|
+
await cp(resolve(template, ".npmrc"), resolve(root, ".npmrc"));
|
|
2010
|
+
} catch (cause) {
|
|
2011
|
+
if (cause === null || typeof cause !== "object" || !("code" in cause) || cause.code !== "ENOENT") {
|
|
2012
|
+
throw cause;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
async function sdkInitCommand(sdk, options = {}) {
|
|
2017
|
+
const pnpmResult = await currentPnpm();
|
|
2018
|
+
if (!pnpmResult.ok) return pnpmResult;
|
|
2019
|
+
if (!supportedPnpm(pnpmResult.value)) {
|
|
2020
|
+
return commandFailure(
|
|
2021
|
+
"pnpm-version-unsupported",
|
|
2022
|
+
"pnpm >=11.7.0 <12",
|
|
2023
|
+
"Activate pnpm 11.7.0 or newer in the pnpm 11 line, then rerun forgeax init.",
|
|
2024
|
+
{ actual: pnpmResult.value, expected: sdk.manifest.requirements.pnpm }
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
const state = {
|
|
2028
|
+
schemaVersion: SDK_INIT_SCHEMA_VERSION,
|
|
2029
|
+
sdkVersion: sdk.manifest.sdkVersion,
|
|
2030
|
+
engineCommit: sdk.manifest.engineCommit,
|
|
2031
|
+
pnpm: pnpmResult.value,
|
|
2032
|
+
node: process.versions.node,
|
|
2033
|
+
platform: process.platform,
|
|
2034
|
+
arch: process.arch
|
|
2035
|
+
};
|
|
2036
|
+
const report = {
|
|
2037
|
+
...state,
|
|
2038
|
+
root: sdk.root,
|
|
2039
|
+
store: sdk.store === void 0 ? "registry" : "offline",
|
|
2040
|
+
onboarding: agentOnboarding(sdk)
|
|
2041
|
+
};
|
|
2042
|
+
if (options.dryRun === true || options.install === false) return { ok: true, value: report };
|
|
2043
|
+
let staging;
|
|
2044
|
+
try {
|
|
2045
|
+
staging = await mkdtemp(resolve(tmpdir(), "forgeax-sdk-init-"));
|
|
2046
|
+
await copyBootstrapInputs(sdk, staging);
|
|
2047
|
+
await execFileAsync("pnpm", sdkBootstrapInstallArgs(sdk.store), {
|
|
2048
|
+
cwd: staging,
|
|
2049
|
+
env: { ...process.env, CI: "true" },
|
|
2050
|
+
maxBuffer: 16 * 1024 * 1024
|
|
2051
|
+
});
|
|
2052
|
+
await mkdir(resolve(sdk.root, ".forgeax"), { recursive: true });
|
|
2053
|
+
await writeFile(sdkInitPath(sdk), `${JSON.stringify(state, null, 2)}
|
|
2054
|
+
`);
|
|
2055
|
+
return { ok: true, value: report };
|
|
2056
|
+
} catch (cause) {
|
|
2057
|
+
return commandFailure(
|
|
2058
|
+
"sdk-init-failed",
|
|
2059
|
+
"the SDK dependency closure to install and build native packages successfully",
|
|
2060
|
+
"Inspect the pnpm output, repair Node/pnpm or platform permissions, then rerun forgeax init from the SDK root.",
|
|
2061
|
+
{ reason: cause instanceof Error ? cause.message : String(cause), sdkRoot: sdk.root }
|
|
2062
|
+
);
|
|
2063
|
+
} finally {
|
|
2064
|
+
if (staging !== void 0) await rm(staging, { recursive: true, force: true });
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
var execFileAsync, SDK_INIT_PATH, SDK_INIT_SCHEMA_VERSION;
|
|
2068
|
+
var init_sdk_bootstrap = __esm({
|
|
2069
|
+
"src/sdk-bootstrap.ts"() {
|
|
2070
|
+
execFileAsync = promisify(execFile);
|
|
2071
|
+
SDK_INIT_PATH = [".forgeax", "sdk-init.json"];
|
|
2072
|
+
SDK_INIT_SCHEMA_VERSION = "1.0.0";
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
// src/sdk-update.ts
|
|
2077
|
+
function parseVersion(version) {
|
|
2078
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
|
|
2079
|
+
version
|
|
2080
|
+
);
|
|
2081
|
+
if (match === null) return void 0;
|
|
2082
|
+
const major = match[1];
|
|
2083
|
+
const minor = match[2];
|
|
2084
|
+
const patch = match[3];
|
|
2085
|
+
if (major === void 0 || minor === void 0 || patch === void 0) return void 0;
|
|
2086
|
+
const prerelease = match[4]?.split(".");
|
|
2087
|
+
if (prerelease?.some(
|
|
2088
|
+
(identifier) => identifier.length === 0 || /^\d+$/.test(identifier) && /^0\d+/.test(identifier)
|
|
2089
|
+
)) {
|
|
2090
|
+
return void 0;
|
|
2091
|
+
}
|
|
2092
|
+
return {
|
|
2093
|
+
core: [major, minor, patch],
|
|
2094
|
+
...prerelease === void 0 ? {} : { prerelease }
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function compareNumericIdentifier(current, latest) {
|
|
2098
|
+
if (current.length !== latest.length) return latest.length > current.length ? 1 : -1;
|
|
2099
|
+
if (current === latest) return 0;
|
|
2100
|
+
return latest > current ? 1 : -1;
|
|
2101
|
+
}
|
|
2102
|
+
function comparePrerelease(current, latest) {
|
|
2103
|
+
const length = Math.max(current.length, latest.length);
|
|
2104
|
+
for (let index = 0; index < length; index += 1) {
|
|
2105
|
+
const currentIdentifier = current[index];
|
|
2106
|
+
const latestIdentifier = latest[index];
|
|
2107
|
+
if (currentIdentifier === void 0) return 1;
|
|
2108
|
+
if (latestIdentifier === void 0) return -1;
|
|
2109
|
+
if (currentIdentifier === latestIdentifier) continue;
|
|
2110
|
+
const currentNumeric = /^\d+$/.test(currentIdentifier);
|
|
2111
|
+
const latestNumeric = /^\d+$/.test(latestIdentifier);
|
|
2112
|
+
if (currentNumeric && latestNumeric) {
|
|
2113
|
+
return compareNumericIdentifier(currentIdentifier, latestIdentifier);
|
|
2114
|
+
}
|
|
2115
|
+
if (currentNumeric !== latestNumeric) return latestNumeric ? -1 : 1;
|
|
2116
|
+
return latestIdentifier > currentIdentifier ? 1 : -1;
|
|
2117
|
+
}
|
|
2118
|
+
return 0;
|
|
2119
|
+
}
|
|
2120
|
+
function newerSdkVersion(currentVersion, latestVersion) {
|
|
2121
|
+
const current = parseVersion(currentVersion);
|
|
2122
|
+
const latest = parseVersion(latestVersion);
|
|
2123
|
+
if (current === void 0 || latest === void 0) return false;
|
|
2124
|
+
for (let index = 0; index < current.core.length; index += 1) {
|
|
2125
|
+
const precedence = compareNumericIdentifier(
|
|
2126
|
+
current.core[index] ?? "0",
|
|
2127
|
+
latest.core[index] ?? "0"
|
|
2128
|
+
);
|
|
2129
|
+
if (precedence !== 0) return precedence > 0;
|
|
2130
|
+
}
|
|
2131
|
+
if (current.prerelease !== void 0 && latest.prerelease === void 0) return true;
|
|
2132
|
+
if (current.prerelease === void 0 || latest.prerelease === void 0) return false;
|
|
2133
|
+
return comparePrerelease(current.prerelease, latest.prerelease) > 0;
|
|
2134
|
+
}
|
|
2135
|
+
function offline() {
|
|
2136
|
+
return ["1", "true"].includes((process.env.npm_config_offline ?? "").toLowerCase());
|
|
2137
|
+
}
|
|
2138
|
+
async function checkSdkUpdate(currentVersion) {
|
|
2139
|
+
if (process.env.FORGEAX_DISABLE_UPDATE_CHECK === "1") {
|
|
2140
|
+
return { status: "skipped", currentVersion, reason: "disabled" };
|
|
2141
|
+
}
|
|
2142
|
+
if (offline()) return { status: "skipped", currentVersion, reason: "offline" };
|
|
2143
|
+
try {
|
|
2144
|
+
const registry = process.env.npm_config_registry ?? "https://registry.npmjs.org/";
|
|
2145
|
+
const response = await fetch(
|
|
2146
|
+
new URL("@forgeax%2Fengine-sdk", `${registry.replace(/\/$/, "")}/`),
|
|
2147
|
+
{
|
|
2148
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
2149
|
+
signal: AbortSignal.timeout(2e3)
|
|
2150
|
+
}
|
|
2151
|
+
);
|
|
2152
|
+
if (!response.ok) throw new Error(`registry-http-${response.status}`);
|
|
2153
|
+
const metadata = await response.json();
|
|
2154
|
+
const latestVersion = metadata["dist-tags"]?.latest;
|
|
2155
|
+
if (typeof latestVersion !== "string") throw new Error("registry-latest-version-missing");
|
|
2156
|
+
return newerSdkVersion(currentVersion, latestVersion) ? {
|
|
2157
|
+
status: "available",
|
|
2158
|
+
currentVersion,
|
|
2159
|
+
latestVersion,
|
|
2160
|
+
migrationRisk: UPDATE_MIGRATION_RISK
|
|
2161
|
+
} : { status: "current", currentVersion, latestVersion };
|
|
2162
|
+
} catch (cause) {
|
|
2163
|
+
return {
|
|
2164
|
+
status: "unavailable",
|
|
2165
|
+
currentVersion,
|
|
2166
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
var UPDATE_MIGRATION_RISK;
|
|
2171
|
+
var init_sdk_update = __esm({
|
|
2172
|
+
"src/sdk-update.ts"() {
|
|
2173
|
+
UPDATE_MIGRATION_RISK = "Install a newer SDK in a separate directory. Existing games remain pinned and are not migrated automatically; review release notes and migrate and test each game before changing its Engine version.";
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
1799
2176
|
function slash(path) {
|
|
1800
2177
|
return path.split(sep).join("/");
|
|
1801
2178
|
}
|
|
@@ -2161,6 +2538,20 @@ async function canonicalProspectivePath(path) {
|
|
|
2161
2538
|
}
|
|
2162
2539
|
}
|
|
2163
2540
|
}
|
|
2541
|
+
async function configureSdkStore(root, store) {
|
|
2542
|
+
if (store === void 0) return;
|
|
2543
|
+
const path = resolve(root, ".npmrc");
|
|
2544
|
+
let content = "";
|
|
2545
|
+
try {
|
|
2546
|
+
content = await readFile(path, "utf8");
|
|
2547
|
+
} catch (cause) {
|
|
2548
|
+
if (!isMissingPathError(cause)) throw cause;
|
|
2549
|
+
}
|
|
2550
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*store-dir\s*=/.test(line));
|
|
2551
|
+
lines.push(`store-dir=${store}`);
|
|
2552
|
+
await writeFile(path, `${lines.join("\n")}
|
|
2553
|
+
`);
|
|
2554
|
+
}
|
|
2164
2555
|
function containsOrEquals(parent, child) {
|
|
2165
2556
|
const path = relative(parent, child);
|
|
2166
2557
|
return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
@@ -2171,25 +2562,14 @@ function nodeSupported() {
|
|
|
2171
2562
|
}
|
|
2172
2563
|
function pnpmSupported(version) {
|
|
2173
2564
|
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
2174
|
-
return major ===
|
|
2175
|
-
}
|
|
2176
|
-
function sdkInstallArgs(store) {
|
|
2177
|
-
if (store === void 0) return ["install", "--frozen-lockfile"];
|
|
2178
|
-
return [
|
|
2179
|
-
"install",
|
|
2180
|
-
"--offline",
|
|
2181
|
-
"--frozen-lockfile",
|
|
2182
|
-
"--side-effects-cache=false",
|
|
2183
|
-
"--store-dir",
|
|
2184
|
-
store
|
|
2185
|
-
];
|
|
2565
|
+
return major === 11 && minor >= 7;
|
|
2186
2566
|
}
|
|
2187
2567
|
async function doctorCommand(options = {}) {
|
|
2188
2568
|
const facts = await readProjectFacts(options.root);
|
|
2189
2569
|
if (!facts.ok) return facts;
|
|
2190
2570
|
let pnpm;
|
|
2191
2571
|
try {
|
|
2192
|
-
pnpm = (await
|
|
2572
|
+
pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
|
|
2193
2573
|
} catch (cause) {
|
|
2194
2574
|
return {
|
|
2195
2575
|
ok: false,
|
|
@@ -2217,7 +2597,7 @@ async function doctorCommand(options = {}) {
|
|
|
2217
2597
|
ok: false,
|
|
2218
2598
|
error: {
|
|
2219
2599
|
code: "pnpm-version-unsupported",
|
|
2220
|
-
expected: "pnpm >=
|
|
2600
|
+
expected: "pnpm >=11.7.0 <12",
|
|
2221
2601
|
hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
|
|
2222
2602
|
detail: { actual: pnpm }
|
|
2223
2603
|
}
|
|
@@ -2256,12 +2636,20 @@ async function doctorCommand(options = {}) {
|
|
|
2256
2636
|
};
|
|
2257
2637
|
}
|
|
2258
2638
|
async function initCommand(options = {}) {
|
|
2259
|
-
const facts = await readProjectFacts(options.root);
|
|
2260
|
-
if (!facts.ok) return facts;
|
|
2261
2639
|
try {
|
|
2262
2640
|
const sdk = await findSdkContext();
|
|
2641
|
+
const root = await canonicalProspectivePath(options.root ?? process.cwd());
|
|
2642
|
+
if (sdk !== void 0 && root === await canonicalProspectivePath(sdk.root)) {
|
|
2643
|
+
return sdkInitCommand(sdk, options);
|
|
2644
|
+
}
|
|
2645
|
+
const facts = await readProjectFacts(root);
|
|
2646
|
+
if (!facts.ok) return facts;
|
|
2263
2647
|
const plan = createInitPlan(facts.value, sdk?.manifest);
|
|
2264
2648
|
if (!plan.ok) return plan;
|
|
2649
|
+
if (sdk !== void 0 && options.dryRun !== true) {
|
|
2650
|
+
const initialized = await requireSdkInitialization(sdk);
|
|
2651
|
+
if (!initialized.ok) return initialized;
|
|
2652
|
+
}
|
|
2265
2653
|
const applied = await applyInitPlan(facts.value, plan.value, options);
|
|
2266
2654
|
if (!applied.ok || options.dryRun === true) return applied;
|
|
2267
2655
|
if (sdk !== void 0) {
|
|
@@ -2271,12 +2659,31 @@ async function initCommand(options = {}) {
|
|
|
2271
2659
|
resolve(template, "pnpm-lock.yaml"),
|
|
2272
2660
|
resolve(facts.value.root, "pnpm-lock.yaml")
|
|
2273
2661
|
);
|
|
2662
|
+
await copyFile(
|
|
2663
|
+
resolve(template, "pnpm-workspace.yaml"),
|
|
2664
|
+
resolve(facts.value.root, "pnpm-workspace.yaml")
|
|
2665
|
+
);
|
|
2666
|
+
try {
|
|
2667
|
+
await readFile(resolve(facts.value.root, ".npmrc"), "utf8");
|
|
2668
|
+
} catch (cause) {
|
|
2669
|
+
if (!isMissingPathError(cause)) throw cause;
|
|
2670
|
+
try {
|
|
2671
|
+
await copyFile(resolve(template, ".npmrc"), resolve(facts.value.root, ".npmrc"));
|
|
2672
|
+
} catch (templateCause) {
|
|
2673
|
+
if (!isMissingPathError(templateCause)) throw templateCause;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2274
2676
|
await copySdkSkills(sdk, facts.value.root);
|
|
2275
2677
|
await installProjectSkills(facts.value.root, sdk.manifest);
|
|
2678
|
+
await configureSdkStore(
|
|
2679
|
+
facts.value.root,
|
|
2680
|
+
sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store)
|
|
2681
|
+
);
|
|
2276
2682
|
}
|
|
2277
2683
|
if (options.install === false) return applied;
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2684
|
+
const store = sdk === void 0 || sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
|
|
2685
|
+
const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkProjectInstallArgs(store);
|
|
2686
|
+
await execFileAsync2("pnpm", installArgs, {
|
|
2280
2687
|
cwd: facts.value.root,
|
|
2281
2688
|
env: { ...process.env, CI: "true" },
|
|
2282
2689
|
maxBuffer: 16 * 1024 * 1024
|
|
@@ -2354,12 +2761,15 @@ async function newCommand(options = {}) {
|
|
|
2354
2761
|
value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
|
|
2355
2762
|
};
|
|
2356
2763
|
}
|
|
2764
|
+
const initialized = await requireSdkInitialization(sdk);
|
|
2765
|
+
if (!initialized.ok) return initialized;
|
|
2357
2766
|
const parent = dirname(root);
|
|
2358
2767
|
await mkdir(parent, { recursive: true });
|
|
2359
2768
|
let staging = await mkdtemp(
|
|
2360
2769
|
resolve(parent, `.${basename(root)}.forgeax-staging-`)
|
|
2361
2770
|
);
|
|
2362
2771
|
const committedNames = [];
|
|
2772
|
+
let committed = false;
|
|
2363
2773
|
try {
|
|
2364
2774
|
for (const name of await readdir(template)) {
|
|
2365
2775
|
await cp(resolve(template, name), resolve(staging, name), {
|
|
@@ -2370,14 +2780,12 @@ async function newCommand(options = {}) {
|
|
|
2370
2780
|
}
|
|
2371
2781
|
await copySdkSkills(sdk, staging);
|
|
2372
2782
|
await installProjectSkills(staging, sdk.manifest);
|
|
2373
|
-
await
|
|
2374
|
-
|
|
2375
|
-
env: { ...process.env, CI: "true" },
|
|
2376
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2377
|
-
});
|
|
2783
|
+
const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
|
|
2784
|
+
await configureSdkStore(staging, store);
|
|
2378
2785
|
if (!targetExists) {
|
|
2379
2786
|
await rename(staging, root);
|
|
2380
2787
|
staging = void 0;
|
|
2788
|
+
committedNames.push(...await readdir(root));
|
|
2381
2789
|
} else {
|
|
2382
2790
|
for (const name of await readdir(staging)) {
|
|
2383
2791
|
await rename(resolve(staging, name), resolve(root, name));
|
|
@@ -2386,29 +2794,57 @@ async function newCommand(options = {}) {
|
|
|
2386
2794
|
await rm(staging, { recursive: true, force: true });
|
|
2387
2795
|
staging = void 0;
|
|
2388
2796
|
}
|
|
2797
|
+
committed = true;
|
|
2798
|
+
await execFileAsync2("pnpm", sdkProjectInstallArgs(store), {
|
|
2799
|
+
cwd: root,
|
|
2800
|
+
env: { ...process.env, CI: "true" },
|
|
2801
|
+
maxBuffer: 16 * 1024 * 1024
|
|
2802
|
+
});
|
|
2803
|
+
const sdkUpdate = await checkSdkUpdate(sdk.manifest.sdkVersion);
|
|
2389
2804
|
return {
|
|
2390
2805
|
ok: true,
|
|
2391
|
-
value: {
|
|
2806
|
+
value: {
|
|
2807
|
+
root,
|
|
2808
|
+
template: templateId,
|
|
2809
|
+
sdkVersion: sdk.manifest.sdkVersion,
|
|
2810
|
+
onboarding: agentOnboarding(sdk, root),
|
|
2811
|
+
sdkUpdate
|
|
2812
|
+
}
|
|
2392
2813
|
};
|
|
2393
2814
|
} catch (cause) {
|
|
2394
2815
|
if (staging !== void 0) await rm(staging, { recursive: true, force: true });
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2816
|
+
if (committed) {
|
|
2817
|
+
if (!targetExists) {
|
|
2818
|
+
await rm(root, { recursive: true, force: true });
|
|
2819
|
+
return { ok: false, error: commandError(cause, "project-create-failed") };
|
|
2820
|
+
}
|
|
2821
|
+
const cleanupNames = /* @__PURE__ */ new Set([...committedNames, "node_modules"]);
|
|
2822
|
+
await Promise.all(
|
|
2823
|
+
[...cleanupNames].map(
|
|
2824
|
+
(name) => rm(resolve(root, name), { recursive: true, force: true })
|
|
2825
|
+
)
|
|
2826
|
+
);
|
|
2827
|
+
} else {
|
|
2828
|
+
await Promise.all(
|
|
2829
|
+
committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2398
2832
|
return { ok: false, error: commandError(cause, "project-create-failed") };
|
|
2399
2833
|
}
|
|
2400
2834
|
} catch (cause) {
|
|
2401
2835
|
return { ok: false, error: commandError(cause, "project-create-failed") };
|
|
2402
2836
|
}
|
|
2403
2837
|
}
|
|
2404
|
-
var
|
|
2838
|
+
var execFileAsync2;
|
|
2405
2839
|
var init_bootstrap_commands = __esm({
|
|
2406
2840
|
"src/bootstrap-commands.ts"() {
|
|
2407
2841
|
init_init();
|
|
2408
2842
|
init_project();
|
|
2409
2843
|
init_sdk();
|
|
2844
|
+
init_sdk_bootstrap();
|
|
2845
|
+
init_sdk_update();
|
|
2410
2846
|
init_skill_install();
|
|
2411
|
-
|
|
2847
|
+
execFileAsync2 = promisify(execFile);
|
|
2412
2848
|
}
|
|
2413
2849
|
});
|
|
2414
2850
|
|
|
@@ -2467,7 +2903,7 @@ function withoutEntry(entries2, id) {
|
|
|
2467
2903
|
}
|
|
2468
2904
|
async function mutateDependency(root, action, dependency) {
|
|
2469
2905
|
if (dependency === void 0) return;
|
|
2470
|
-
await
|
|
2906
|
+
await execFileAsync3("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
|
|
2471
2907
|
}
|
|
2472
2908
|
async function pluginInstallCommand(options) {
|
|
2473
2909
|
const root = resolve(options.root ?? process.cwd());
|
|
@@ -2569,489 +3005,10 @@ async function pluginUninstallCommand(options) {
|
|
|
2569
3005
|
};
|
|
2570
3006
|
}
|
|
2571
3007
|
}
|
|
2572
|
-
var
|
|
3008
|
+
var execFileAsync3;
|
|
2573
3009
|
var init_plugin_authoring = __esm({
|
|
2574
3010
|
"src/plugin-authoring.ts"() {
|
|
2575
|
-
|
|
2576
|
-
}
|
|
2577
|
-
});
|
|
2578
|
-
var RhiError;
|
|
2579
|
-
var init_dist2 = __esm({
|
|
2580
|
-
"../rhi/dist/index.mjs"() {
|
|
2581
|
-
RhiError = class extends Error {
|
|
2582
|
-
code;
|
|
2583
|
-
expected;
|
|
2584
|
-
hint;
|
|
2585
|
-
detail;
|
|
2586
|
-
constructor(args2) {
|
|
2587
|
-
super(`[RhiError ${args2.code}] expected: ${args2.expected}; hint: ${args2.hint}`);
|
|
2588
|
-
this.name = "RhiError";
|
|
2589
|
-
this.code = args2.code;
|
|
2590
|
-
this.expected = args2.expected;
|
|
2591
|
-
this.hint = args2.hint;
|
|
2592
|
-
this.detail = args2.detail;
|
|
2593
|
-
}
|
|
2594
|
-
};
|
|
2595
|
-
}
|
|
2596
|
-
});
|
|
2597
|
-
function readPassLabel(desc) {
|
|
2598
|
-
if (desc && typeof desc.label === "string" && desc.label.length > 0) {
|
|
2599
|
-
return desc.label;
|
|
2600
|
-
}
|
|
2601
|
-
return "<unnamed>";
|
|
2602
|
-
}
|
|
2603
|
-
function readRecord(handle) {
|
|
2604
|
-
if (handle === null || typeof handle !== "object") return void 0;
|
|
2605
|
-
const tagged = handle;
|
|
2606
|
-
return tagged[BOOKKEEPING_KEY];
|
|
2607
|
-
}
|
|
2608
|
-
function isHandleDestroyed(handle) {
|
|
2609
|
-
if (readRecord(handle)?.destroyed === true) {
|
|
2610
|
-
return err(
|
|
2611
|
-
new RhiError({
|
|
2612
|
-
code: "destroy-after-destroy",
|
|
2613
|
-
expected: "GPU buffer/texture handle has not been destroyed yet",
|
|
2614
|
-
hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
|
|
2615
|
-
})
|
|
2616
|
-
);
|
|
2617
|
-
}
|
|
2618
|
-
return ok(void 0);
|
|
2619
|
-
}
|
|
2620
|
-
function acquireCanvasContext(_canvas) {
|
|
2621
|
-
return ok(new RhiNullCanvasContext());
|
|
2622
|
-
}
|
|
2623
|
-
function createShaderModule(_device, _desc) {
|
|
2624
|
-
return Promise.resolve(ok({}));
|
|
2625
|
-
}
|
|
2626
|
-
function requestAdapter(_opts, _compatibleSurface) {
|
|
2627
|
-
return Promise.resolve(ok(new RhiNullAdapter()));
|
|
2628
|
-
}
|
|
2629
|
-
var RhiNullRenderPassEncoder, RhiNullComputePassEncoder, DeviceCounter, RhiNullCommandEncoder, BOOKKEEPING_KEY, Bookkeeper, nextDeviceId, RhiNullDevice, EMPTY_FEATURES, EMPTY_LIMITS, NEVER, RhiNullQueue, RhiNullAdapter, RhiNullCanvasContext, rhi;
|
|
2630
|
-
var init_dist3 = __esm({
|
|
2631
|
-
"../rhi-null/dist/index.mjs"() {
|
|
2632
|
-
init_dist2();
|
|
2633
|
-
RhiNullRenderPassEncoder = class {
|
|
2634
|
-
/** Number of draw* calls issued on this pass (AC-06 readback). */
|
|
2635
|
-
drawCount = 0;
|
|
2636
|
-
bindGroupCount = 0;
|
|
2637
|
-
/** Most recent setVertexBuffer / setBindGroup ownership validation; ok unless
|
|
2638
|
-
* a foreign handle was passed (AC-09 readback). */
|
|
2639
|
-
lastValidation = ok(void 0);
|
|
2640
|
-
bookkeeper;
|
|
2641
|
-
counter;
|
|
2642
|
-
passName;
|
|
2643
|
-
constructor(bookkeeper, counter, passName) {
|
|
2644
|
-
this.bookkeeper = bookkeeper;
|
|
2645
|
-
this.counter = counter;
|
|
2646
|
-
this.passName = passName;
|
|
2647
|
-
}
|
|
2648
|
-
setPipeline(_pipeline) {
|
|
2649
|
-
}
|
|
2650
|
-
setVertexBuffer(_slot, buffer, _offset, _size) {
|
|
2651
|
-
this.lastValidation = this.bookkeeper.validateOwnership(buffer);
|
|
2652
|
-
}
|
|
2653
|
-
setIndexBuffer(_buffer, _format, _offset, _size) {
|
|
2654
|
-
}
|
|
2655
|
-
setBindGroup(_index, bindGroup, _dynamicOffsetsData, _dynamicOffsetsDataStart, _dynamicOffsetsDataLength) {
|
|
2656
|
-
this.bindGroupCount++;
|
|
2657
|
-
this.counter?.recordBindGroup();
|
|
2658
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2659
|
-
}
|
|
2660
|
-
draw(_vertexCount, _instanceCount, _firstVertex, _firstInstance) {
|
|
2661
|
-
this.drawCount++;
|
|
2662
|
-
this.counter?.recordDraw();
|
|
2663
|
-
}
|
|
2664
|
-
drawIndexed(_indexCount, _instanceCount, _firstIndex, _baseVertex, _firstInstance) {
|
|
2665
|
-
this.drawCount++;
|
|
2666
|
-
this.counter?.recordDraw();
|
|
2667
|
-
}
|
|
2668
|
-
end() {
|
|
2669
|
-
this.counter?.recordPassName(this.passName);
|
|
2670
|
-
}
|
|
2671
|
-
setViewport(_x, _y, _w, _h, _minDepth, _maxDepth) {
|
|
2672
|
-
}
|
|
2673
|
-
setScissorRect(_x, _y, _w, _h) {
|
|
2674
|
-
}
|
|
2675
|
-
setBlendConstant(_color) {
|
|
2676
|
-
}
|
|
2677
|
-
setStencilReference(_reference) {
|
|
2678
|
-
}
|
|
2679
|
-
drawIndirect(_indirectBuffer, _indirectOffset) {
|
|
2680
|
-
this.drawCount++;
|
|
2681
|
-
this.counter?.recordDraw();
|
|
2682
|
-
}
|
|
2683
|
-
drawIndexedIndirect(_indirectBuffer, _indirectOffset) {
|
|
2684
|
-
this.drawCount++;
|
|
2685
|
-
this.counter?.recordDraw();
|
|
2686
|
-
}
|
|
2687
|
-
pushDebugGroup(_groupLabel) {
|
|
2688
|
-
}
|
|
2689
|
-
popDebugGroup() {
|
|
2690
|
-
}
|
|
2691
|
-
insertDebugMarker(_markerLabel) {
|
|
2692
|
-
}
|
|
2693
|
-
executeBundles(_bundles) {
|
|
2694
|
-
return ok(void 0);
|
|
2695
|
-
}
|
|
2696
|
-
beginOcclusionQuery(_queryIndex) {
|
|
2697
|
-
return ok(void 0);
|
|
2698
|
-
}
|
|
2699
|
-
endOcclusionQuery() {
|
|
2700
|
-
return ok(void 0);
|
|
2701
|
-
}
|
|
2702
|
-
};
|
|
2703
|
-
RhiNullComputePassEncoder = class {
|
|
2704
|
-
/** Number of dispatchWorkgroups calls issued on this pass (readback). */
|
|
2705
|
-
dispatchCount = 0;
|
|
2706
|
-
/** Most recent setBindGroup ownership validation (AC-09 readback). */
|
|
2707
|
-
lastValidation = ok(void 0);
|
|
2708
|
-
bookkeeper;
|
|
2709
|
-
counter;
|
|
2710
|
-
passName;
|
|
2711
|
-
constructor(bookkeeper, counter, passName) {
|
|
2712
|
-
this.bookkeeper = bookkeeper;
|
|
2713
|
-
this.counter = counter;
|
|
2714
|
-
this.passName = passName;
|
|
2715
|
-
}
|
|
2716
|
-
setPipeline(_pipeline) {
|
|
2717
|
-
}
|
|
2718
|
-
setBindGroup(_index, bindGroup, _dynamicOffsets) {
|
|
2719
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2720
|
-
}
|
|
2721
|
-
dispatchWorkgroups(_x, _y, _z) {
|
|
2722
|
-
this.dispatchCount++;
|
|
2723
|
-
this.counter?.recordDispatch();
|
|
2724
|
-
}
|
|
2725
|
-
dispatchWorkgroupsIndirect(indirectBuffer, _indirectOffset) {
|
|
2726
|
-
this.lastValidation = this.bookkeeper.validateOwnership(indirectBuffer);
|
|
2727
|
-
this.dispatchCount++;
|
|
2728
|
-
this.counter?.recordDispatch();
|
|
2729
|
-
}
|
|
2730
|
-
end() {
|
|
2731
|
-
this.counter?.recordPassName(this.passName);
|
|
2732
|
-
}
|
|
2733
|
-
};
|
|
2734
|
-
DeviceCounter = class {
|
|
2735
|
-
constructor(device) {
|
|
2736
|
-
this.device = device;
|
|
2737
|
-
}
|
|
2738
|
-
device;
|
|
2739
|
-
recordDraw() {
|
|
2740
|
-
this.device.totalDrawCount++;
|
|
2741
|
-
}
|
|
2742
|
-
recordDispatch() {
|
|
2743
|
-
this.device.totalDispatchCount++;
|
|
2744
|
-
}
|
|
2745
|
-
recordBindGroup() {
|
|
2746
|
-
this.device.totalBindGroupCount++;
|
|
2747
|
-
}
|
|
2748
|
-
recordPassName(name) {
|
|
2749
|
-
this.device.framePassNames.push(name);
|
|
2750
|
-
}
|
|
2751
|
-
};
|
|
2752
|
-
RhiNullCommandEncoder = class {
|
|
2753
|
-
bookkeeper;
|
|
2754
|
-
counter;
|
|
2755
|
-
constructor(bookkeeper, device) {
|
|
2756
|
-
this.bookkeeper = bookkeeper;
|
|
2757
|
-
this.counter = new DeviceCounter(device);
|
|
2758
|
-
}
|
|
2759
|
-
beginRenderPass(desc) {
|
|
2760
|
-
const label = readPassLabel(desc);
|
|
2761
|
-
return new RhiNullRenderPassEncoder(this.bookkeeper, this.counter, label);
|
|
2762
|
-
}
|
|
2763
|
-
beginComputePass(desc) {
|
|
2764
|
-
const label = readPassLabel(desc);
|
|
2765
|
-
return new RhiNullComputePassEncoder(this.bookkeeper, this.counter, label);
|
|
2766
|
-
}
|
|
2767
|
-
copyBufferToBuffer(_source, _sourceOffsetOrDestination, _destinationOrSize, _destinationOffset, _size) {
|
|
2768
|
-
}
|
|
2769
|
-
copyBufferToTexture(_source, _destination, _copySize) {
|
|
2770
|
-
}
|
|
2771
|
-
copyTextureToBuffer(_source, _destination, _copySize) {
|
|
2772
|
-
}
|
|
2773
|
-
copyTextureToTexture(_source, _destination, _copySize) {
|
|
2774
|
-
}
|
|
2775
|
-
clearBuffer(_buffer, _offset, _size) {
|
|
2776
|
-
}
|
|
2777
|
-
resolveQuerySet(_querySet, _firstQuery, _queryCount, _destination, _destinationOffset) {
|
|
2778
|
-
return ok(void 0);
|
|
2779
|
-
}
|
|
2780
|
-
writeTimestamp(_querySet, _queryIndex) {
|
|
2781
|
-
}
|
|
2782
|
-
pushDebugGroup(_groupLabel) {
|
|
2783
|
-
}
|
|
2784
|
-
popDebugGroup() {
|
|
2785
|
-
}
|
|
2786
|
-
insertDebugMarker(_markerLabel) {
|
|
2787
|
-
}
|
|
2788
|
-
finish() {
|
|
2789
|
-
return ok(this.bookkeeper.register("CommandBuffer"));
|
|
2790
|
-
}
|
|
2791
|
-
};
|
|
2792
|
-
BOOKKEEPING_KEY = /* @__PURE__ */ Symbol("forgeax-rhi-null-bookkeeping");
|
|
2793
|
-
Bookkeeper = class {
|
|
2794
|
-
deviceId;
|
|
2795
|
-
nextHandleId = 0;
|
|
2796
|
-
records = /* @__PURE__ */ new Map();
|
|
2797
|
-
constructor(deviceId) {
|
|
2798
|
-
this.deviceId = deviceId;
|
|
2799
|
-
}
|
|
2800
|
-
/**
|
|
2801
|
-
* Register a freshly-minted handle of the given kind, returning a plain
|
|
2802
|
-
* object that carries its ledger row. The caller casts the return value to
|
|
2803
|
-
* the concrete brand (`as unknown as Buffer` etc.).
|
|
2804
|
-
*/
|
|
2805
|
-
register(kind) {
|
|
2806
|
-
const id = this.nextHandleId++;
|
|
2807
|
-
const record = {
|
|
2808
|
-
id,
|
|
2809
|
-
kind,
|
|
2810
|
-
destroyed: false,
|
|
2811
|
-
sourceDeviceId: this.deviceId
|
|
2812
|
-
};
|
|
2813
|
-
this.records.set(id, record);
|
|
2814
|
-
return { [BOOKKEEPING_KEY]: record };
|
|
2815
|
-
}
|
|
2816
|
-
/**
|
|
2817
|
-
* Mark a handle destroyed. Fail-fasts on a second destroy
|
|
2818
|
-
* ('destroy-after-destroy') or on a handle issued by a different device
|
|
2819
|
-
* ('rhi-not-available'); otherwise flips the destroyed flag and returns ok.
|
|
2820
|
-
*/
|
|
2821
|
-
destroy(handle) {
|
|
2822
|
-
const ownership = this.validateOwnership(handle);
|
|
2823
|
-
if (!ownership.ok) return ownership;
|
|
2824
|
-
const destroyedCheck = isHandleDestroyed(handle);
|
|
2825
|
-
if (!destroyedCheck.ok) return destroyedCheck;
|
|
2826
|
-
ownership.value.destroyed = true;
|
|
2827
|
-
return ok(void 0);
|
|
2828
|
-
}
|
|
2829
|
-
/**
|
|
2830
|
-
* Report whether a handle has already been destroyed (true once destroy()
|
|
2831
|
-
* has flipped its flag). Used by command-stream methods that must not
|
|
2832
|
-
* consume a stale handle.
|
|
2833
|
-
*/
|
|
2834
|
-
isDestroyed(handle) {
|
|
2835
|
-
return readRecord(handle)?.destroyed === true;
|
|
2836
|
-
}
|
|
2837
|
-
/**
|
|
2838
|
-
* Validate that a handle was issued by THIS device. Returns the ledger row on
|
|
2839
|
-
* success so callers can mutate it (e.g. flip destroyed); returns
|
|
2840
|
-
* 'rhi-not-available' for a foreign handle (AC-09 — no silent pass).
|
|
2841
|
-
*/
|
|
2842
|
-
validateOwnership(handle) {
|
|
2843
|
-
const record = readRecord(handle);
|
|
2844
|
-
if (record === void 0 || record.sourceDeviceId !== this.deviceId) {
|
|
2845
|
-
return err(
|
|
2846
|
-
new RhiError({
|
|
2847
|
-
code: "rhi-not-available",
|
|
2848
|
-
expected: "handle was issued by this RhiNull device",
|
|
2849
|
-
hint: "do not pass a handle created on a different RhiNull device into this device; create resources on the device they are used with"
|
|
2850
|
-
})
|
|
2851
|
-
);
|
|
2852
|
-
}
|
|
2853
|
-
return ok(record);
|
|
2854
|
-
}
|
|
2855
|
-
/**
|
|
2856
|
-
* Return all ledger rows as a readonly array. M3 unit tests (w17) read this
|
|
2857
|
-
* to assert create/destroy pairing, BGL/PSO shape counts, and resource
|
|
2858
|
-
* lifecycle coverage (AC-05/06/07). Returns a snapshot of the current Map
|
|
2859
|
-
* so callers can iterate without a stale reference after further mutations.
|
|
2860
|
-
*/
|
|
2861
|
-
allRecords() {
|
|
2862
|
-
return [...this.records.values()];
|
|
2863
|
-
}
|
|
2864
|
-
/** Report the total number of records in the ledger. */
|
|
2865
|
-
recordCount() {
|
|
2866
|
-
return this.records.size;
|
|
2867
|
-
}
|
|
2868
|
-
};
|
|
2869
|
-
nextDeviceId = 0;
|
|
2870
|
-
RhiNullDevice = class {
|
|
2871
|
-
internalBookkeeper;
|
|
2872
|
-
nullQueue;
|
|
2873
|
-
encoderFactory;
|
|
2874
|
-
/** Per-frame total draw count across all pass encoders executed this frame
|
|
2875
|
-
* (aggregated by the command encoder on finish, then reset). M3 unit tests
|
|
2876
|
-
* (w17) read this to assert draw count >= 1 (AC-06). */
|
|
2877
|
-
totalDrawCount = 0;
|
|
2878
|
-
/** Per-frame total direct and indirect compute dispatch count. */
|
|
2879
|
-
totalDispatchCount = 0;
|
|
2880
|
-
/** Per-frame total bind group set count (AC-06 / AC-05 readback). */
|
|
2881
|
-
totalBindGroupCount = 0;
|
|
2882
|
-
/** Per-frame pass names executed this frame, in schedule order (AC-04). */
|
|
2883
|
-
framePassNames = [];
|
|
2884
|
-
/** The per-device handle ledger — exposed so M3 tests can assert create/destroy
|
|
2885
|
-
* pairing and BGL/PSO shape counts (AC-05/06/07). */
|
|
2886
|
-
get bookkeeper() {
|
|
2887
|
-
return this.internalBookkeeper;
|
|
2888
|
-
}
|
|
2889
|
-
constructor(queue, encoderFactory) {
|
|
2890
|
-
this.internalBookkeeper = new Bookkeeper(nextDeviceId++);
|
|
2891
|
-
this.nullQueue = queue;
|
|
2892
|
-
this.encoderFactory = encoderFactory;
|
|
2893
|
-
}
|
|
2894
|
-
get caps() {
|
|
2895
|
-
return {
|
|
2896
|
-
backendKind: "null",
|
|
2897
|
-
compute: true,
|
|
2898
|
-
timestampQuery: false,
|
|
2899
|
-
timestampPeriodNanoseconds: null,
|
|
2900
|
-
indirectDrawing: true,
|
|
2901
|
-
textureCompressionBc: false,
|
|
2902
|
-
textureCompressionEtc2: false,
|
|
2903
|
-
textureCompressionAstc: false,
|
|
2904
|
-
// 3 wgpu-native-only reserved flags stay false on non-native backends
|
|
2905
|
-
// (D-5); the headless backend is not a native runtime.
|
|
2906
|
-
multiDrawIndirect: false,
|
|
2907
|
-
pushConstants: false,
|
|
2908
|
-
textureBindingArray: false,
|
|
2909
|
-
samplerAliasing: true,
|
|
2910
|
-
firstInstanceIndirect: true,
|
|
2911
|
-
storageBuffer: true,
|
|
2912
|
-
storageTexture: true,
|
|
2913
|
-
rgba16floatRenderable: true,
|
|
2914
|
-
rg11b10ufloatRenderable: true,
|
|
2915
|
-
float32Filterable: true,
|
|
2916
|
-
maxColorAttachments: 8
|
|
2917
|
-
};
|
|
2918
|
-
}
|
|
2919
|
-
get features() {
|
|
2920
|
-
return EMPTY_FEATURES;
|
|
2921
|
-
}
|
|
2922
|
-
get limits() {
|
|
2923
|
-
return EMPTY_LIMITS;
|
|
2924
|
-
}
|
|
2925
|
-
get queue() {
|
|
2926
|
-
return this.nullQueue;
|
|
2927
|
-
}
|
|
2928
|
-
// forgeax-async-whitelist: dom-native — spec `GPUDevice.lost` Promise
|
|
2929
|
-
// passthrough. The headless backend never loses a device (no GPU), so the
|
|
2930
|
-
// Promise stays unsettled for the lifetime of the device, mirroring a live
|
|
2931
|
-
// device that never transitions to the lost state.
|
|
2932
|
-
get lost() {
|
|
2933
|
-
return NEVER;
|
|
2934
|
-
}
|
|
2935
|
-
createBuffer(_desc) {
|
|
2936
|
-
return ok(this.internalBookkeeper.register("Buffer"));
|
|
2937
|
-
}
|
|
2938
|
-
createTexture(_desc) {
|
|
2939
|
-
return ok(this.internalBookkeeper.register("Texture"));
|
|
2940
|
-
}
|
|
2941
|
-
destroyBuffer(buf) {
|
|
2942
|
-
return this.internalBookkeeper.destroy(buf);
|
|
2943
|
-
}
|
|
2944
|
-
destroyQuerySet(querySet) {
|
|
2945
|
-
return this.internalBookkeeper.destroy(querySet);
|
|
2946
|
-
}
|
|
2947
|
-
destroyTexture(tex) {
|
|
2948
|
-
return this.internalBookkeeper.destroy(tex);
|
|
2949
|
-
}
|
|
2950
|
-
createTextureView(_texture, _desc) {
|
|
2951
|
-
return ok(this.internalBookkeeper.register("TextureView"));
|
|
2952
|
-
}
|
|
2953
|
-
createSampler(_desc) {
|
|
2954
|
-
return ok(this.internalBookkeeper.register("Sampler"));
|
|
2955
|
-
}
|
|
2956
|
-
createBindGroupLayout(_desc) {
|
|
2957
|
-
return ok(this.internalBookkeeper.register("BindGroupLayout"));
|
|
2958
|
-
}
|
|
2959
|
-
createBindGroup(_desc) {
|
|
2960
|
-
return ok(this.internalBookkeeper.register("BindGroup"));
|
|
2961
|
-
}
|
|
2962
|
-
createPipelineLayout(_desc) {
|
|
2963
|
-
return ok(this.internalBookkeeper.register("PipelineLayout"));
|
|
2964
|
-
}
|
|
2965
|
-
createRenderPipeline(_desc) {
|
|
2966
|
-
return ok(this.makePipeline("RenderPipeline"));
|
|
2967
|
-
}
|
|
2968
|
-
createComputePipeline(_desc) {
|
|
2969
|
-
return ok(this.makePipeline("ComputePipeline"));
|
|
2970
|
-
}
|
|
2971
|
-
createQuerySet(desc) {
|
|
2972
|
-
if (desc.type === "timestamp") {
|
|
2973
|
-
return err(
|
|
2974
|
-
new RhiError({
|
|
2975
|
-
code: "feature-not-enabled",
|
|
2976
|
-
expected: "caps.timestampQuery === true (timestamp-query feature)",
|
|
2977
|
-
hint: "RhiNull is structural-only and cannot produce GPU timestamp ticks"
|
|
2978
|
-
})
|
|
2979
|
-
);
|
|
2980
|
-
}
|
|
2981
|
-
return ok(this.internalBookkeeper.register("QuerySet"));
|
|
2982
|
-
}
|
|
2983
|
-
createCommandEncoder(_desc) {
|
|
2984
|
-
return ok(this.encoderFactory(this.internalBookkeeper, this));
|
|
2985
|
-
}
|
|
2986
|
-
/**
|
|
2987
|
-
* Mint a pipeline handle whose object also carries the no-op
|
|
2988
|
-
* `getBindGroupLayout(index)` ops method (D-2). The prod auto-layout path
|
|
2989
|
-
* (debug-draw.ts) and the existing mock unit tests both call
|
|
2990
|
-
* `pipeline.getBindGroupLayout(n)`; returning a legal BindGroupLayout brand
|
|
2991
|
-
* (recorded in the ledger) keeps those consumers from crashing on a missing
|
|
2992
|
-
* method.
|
|
2993
|
-
*/
|
|
2994
|
-
makePipeline(kind) {
|
|
2995
|
-
const handle = this.internalBookkeeper.register(kind);
|
|
2996
|
-
const getBindGroupLayout = (_index) => this.internalBookkeeper.register("BindGroupLayout");
|
|
2997
|
-
return Object.assign(handle, { getBindGroupLayout });
|
|
2998
|
-
}
|
|
2999
|
-
};
|
|
3000
|
-
EMPTY_FEATURES = /* @__PURE__ */ new Set();
|
|
3001
|
-
EMPTY_LIMITS = {};
|
|
3002
|
-
NEVER = new Promise(() => {
|
|
3003
|
-
});
|
|
3004
|
-
RhiNullQueue = class {
|
|
3005
|
-
writeBuffer(_buffer, _bufferOffset, _data, _dataOffset, _size) {
|
|
3006
|
-
return ok(void 0);
|
|
3007
|
-
}
|
|
3008
|
-
writeTexture(_destination, _data, _dataLayout, _size) {
|
|
3009
|
-
return ok(void 0);
|
|
3010
|
-
}
|
|
3011
|
-
copyExternalImageToTexture(_source, _destination, _copySize) {
|
|
3012
|
-
return ok(void 0);
|
|
3013
|
-
}
|
|
3014
|
-
submit(_commandBuffers) {
|
|
3015
|
-
return ok(void 0);
|
|
3016
|
-
}
|
|
3017
|
-
// forgeax-async-whitelist: dom-native — spec `GPUQueue.onSubmittedWorkDone`
|
|
3018
|
-
// never rejects. The headless backend has no pending GPU work, so it resolves
|
|
3019
|
-
// immediately (AC-12: read-back idioms must not hang).
|
|
3020
|
-
onSubmittedWorkDone() {
|
|
3021
|
-
return Promise.resolve(void 0);
|
|
3022
|
-
}
|
|
3023
|
-
};
|
|
3024
|
-
RhiNullAdapter = class {
|
|
3025
|
-
features = /* @__PURE__ */ new Set();
|
|
3026
|
-
limits = {};
|
|
3027
|
-
// forgeax-async-whitelist is not needed: this returns Promise<Result<...>>
|
|
3028
|
-
// per the spec contract; never rejects.
|
|
3029
|
-
requestDevice(_opts) {
|
|
3030
|
-
const device = new RhiNullDevice(
|
|
3031
|
-
new RhiNullQueue(),
|
|
3032
|
-
(bookkeeper, dev) => new RhiNullCommandEncoder(bookkeeper, dev)
|
|
3033
|
-
);
|
|
3034
|
-
return Promise.resolve(ok(device));
|
|
3035
|
-
}
|
|
3036
|
-
};
|
|
3037
|
-
RhiNullCanvasContext = class {
|
|
3038
|
-
configure(_desc) {
|
|
3039
|
-
return ok(void 0);
|
|
3040
|
-
}
|
|
3041
|
-
unconfigure() {
|
|
3042
|
-
}
|
|
3043
|
-
getConfiguration() {
|
|
3044
|
-
return void 0;
|
|
3045
|
-
}
|
|
3046
|
-
getCurrentTexture() {
|
|
3047
|
-
return ok({});
|
|
3048
|
-
}
|
|
3049
|
-
};
|
|
3050
|
-
rhi = {
|
|
3051
|
-
requestAdapter,
|
|
3052
|
-
acquireCanvasContext,
|
|
3053
|
-
createShaderModule
|
|
3054
|
-
};
|
|
3011
|
+
execFileAsync3 = promisify(execFile);
|
|
3055
3012
|
}
|
|
3056
3013
|
});
|
|
3057
3014
|
function createCliRhiDebugOperationContext() {
|
|
@@ -3059,7 +3016,7 @@ function createCliRhiDebugOperationContext() {
|
|
|
3059
3016
|
captureFrame: async () => err(
|
|
3060
3017
|
createRhiDebugError("capture-unavailable", {
|
|
3061
3018
|
stage: "capture",
|
|
3062
|
-
cause: "the standalone CLI has no live App capture provider"
|
|
3019
|
+
cause: "the standalone CLI has no live App capture provider; start a recorder-enabled live host and invoke its rhiCapture root"
|
|
3063
3020
|
})
|
|
3064
3021
|
),
|
|
3065
3022
|
async readArtifact(artifact) {
|
|
@@ -3122,7 +3079,6 @@ function createCliRhiDebugOperationContext() {
|
|
|
3122
3079
|
}
|
|
3123
3080
|
var init_cli_context = __esm({
|
|
3124
3081
|
"src/rhi-debug/cli-context.ts"() {
|
|
3125
|
-
init_dist3();
|
|
3126
3082
|
}
|
|
3127
3083
|
});
|
|
3128
3084
|
function createRhiDebugOperationContext(host) {
|
|
@@ -3427,7 +3383,7 @@ async function sdkInstallCommand(options) {
|
|
|
3427
3383
|
try {
|
|
3428
3384
|
staging = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-staging-`));
|
|
3429
3385
|
download = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-download-`));
|
|
3430
|
-
await
|
|
3386
|
+
await execFileAsync4(
|
|
3431
3387
|
process.env.FORGEAX_NPM_CLIENT ?? "npm",
|
|
3432
3388
|
[
|
|
3433
3389
|
"install",
|
|
@@ -3460,7 +3416,15 @@ async function sdkInstallCommand(options) {
|
|
|
3460
3416
|
await rename(resolve(staging, name), resolve(root, name));
|
|
3461
3417
|
}
|
|
3462
3418
|
}
|
|
3463
|
-
return {
|
|
3419
|
+
return {
|
|
3420
|
+
ok: true,
|
|
3421
|
+
value: {
|
|
3422
|
+
root,
|
|
3423
|
+
sdkVersion: version,
|
|
3424
|
+
source: "@forgeax/engine-sdk",
|
|
3425
|
+
next: { cwd: root, argv: ["node", "./bin/forgeax.mjs", "init"] }
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3464
3428
|
} catch (cause) {
|
|
3465
3429
|
return { ok: false, error: commandError(cause, "sdk-install-failed") };
|
|
3466
3430
|
} finally {
|
|
@@ -3468,11 +3432,11 @@ async function sdkInstallCommand(options) {
|
|
|
3468
3432
|
if (download !== void 0) await rm(download, { recursive: true, force: true });
|
|
3469
3433
|
}
|
|
3470
3434
|
}
|
|
3471
|
-
var
|
|
3435
|
+
var execFileAsync4;
|
|
3472
3436
|
var init_sdk_install = __esm({
|
|
3473
3437
|
"src/sdk-install.ts"() {
|
|
3474
3438
|
init_project();
|
|
3475
|
-
|
|
3439
|
+
execFileAsync4 = promisify(execFile);
|
|
3476
3440
|
}
|
|
3477
3441
|
});
|
|
3478
3442
|
async function shaderCheckCommand(options = {}) {
|
|
@@ -3543,6 +3507,21 @@ __export(commands_exports, {
|
|
|
3543
3507
|
function runRhiDebugCommand(name, input, context) {
|
|
3544
3508
|
return runRhiDebugOperation(name, input, context);
|
|
3545
3509
|
}
|
|
3510
|
+
async function materializeViteDevPort(port) {
|
|
3511
|
+
if (port !== 0) return port;
|
|
3512
|
+
const reservation = createServer();
|
|
3513
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
3514
|
+
reservation.once("error", rejectListen);
|
|
3515
|
+
reservation.listen(0, "127.0.0.1", resolveListen);
|
|
3516
|
+
});
|
|
3517
|
+
const address = reservation.address();
|
|
3518
|
+
const assigned = typeof address === "object" && address !== null ? address.port : 0;
|
|
3519
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
3520
|
+
reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
|
|
3521
|
+
});
|
|
3522
|
+
if (assigned === 0) throw new Error("OS did not assign an ephemeral preview port");
|
|
3523
|
+
return assigned;
|
|
3524
|
+
}
|
|
3546
3525
|
async function buildCommand(options = {}) {
|
|
3547
3526
|
const facts = await readProjectFacts(options.root);
|
|
3548
3527
|
if (!facts.ok) return facts;
|
|
@@ -3645,13 +3624,38 @@ async function devCommand(options = {}) {
|
|
|
3645
3624
|
const facts = await readProjectFacts(options.root);
|
|
3646
3625
|
if (!facts.ok) return facts;
|
|
3647
3626
|
const previous = process.cwd();
|
|
3627
|
+
let server;
|
|
3648
3628
|
try {
|
|
3629
|
+
const { createServer: createServer2 } = await import('vite');
|
|
3630
|
+
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_host(), host_exports));
|
|
3631
|
+
const port = resolveProjectPort(options.port);
|
|
3632
|
+
const vitePort = { ...port, port: await materializeViteDevPort(port.port) };
|
|
3649
3633
|
process.chdir(facts.value.root);
|
|
3650
|
-
|
|
3651
|
-
|
|
3634
|
+
server = await createServer2(
|
|
3635
|
+
await createViteConfig2(facts.value, "serve", "/", {
|
|
3636
|
+
server: vitePort
|
|
3637
|
+
})
|
|
3638
|
+
);
|
|
3639
|
+
await server.listen(vitePort.port);
|
|
3652
3640
|
if (options.json !== true) server.printUrls();
|
|
3653
|
-
return {
|
|
3641
|
+
return {
|
|
3642
|
+
ok: true,
|
|
3643
|
+
value: {
|
|
3644
|
+
root: facts.value.root,
|
|
3645
|
+
urls: server.resolvedUrls,
|
|
3646
|
+
mode: "dev",
|
|
3647
|
+
serves: "source",
|
|
3648
|
+
capabilities: {
|
|
3649
|
+
"rhi.capture": {
|
|
3650
|
+
available: false,
|
|
3651
|
+
realm: "host",
|
|
3652
|
+
reason: "standalone-dev-server-has-no-live-app-cli-attachment"
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3654
3657
|
} catch (cause) {
|
|
3658
|
+
await server?.close();
|
|
3655
3659
|
process.chdir(previous);
|
|
3656
3660
|
return { ok: false, error: commandError(cause, "dev-server-failed") };
|
|
3657
3661
|
}
|
|
@@ -3661,15 +3665,31 @@ async function previewCommand(options = {}) {
|
|
|
3661
3665
|
const verified = await verifyDist(resolve(root, "dist"));
|
|
3662
3666
|
if (!verified.ok) return verified;
|
|
3663
3667
|
try {
|
|
3668
|
+
const port = resolveProjectPort(options.port);
|
|
3664
3669
|
const server = await preview({
|
|
3665
3670
|
root,
|
|
3666
3671
|
configFile: false,
|
|
3667
3672
|
base: verified.value.base,
|
|
3668
|
-
preview: { open: false, port
|
|
3673
|
+
preview: { open: false, ...port },
|
|
3669
3674
|
build: { outDir: resolve(root, "dist") }
|
|
3670
3675
|
});
|
|
3671
3676
|
if (options.json !== true) server.printUrls();
|
|
3672
|
-
return {
|
|
3677
|
+
return {
|
|
3678
|
+
ok: true,
|
|
3679
|
+
value: {
|
|
3680
|
+
root,
|
|
3681
|
+
urls: server.resolvedUrls,
|
|
3682
|
+
mode: "preview",
|
|
3683
|
+
serves: "dist",
|
|
3684
|
+
capabilities: {
|
|
3685
|
+
"rhi.capture": {
|
|
3686
|
+
available: false,
|
|
3687
|
+
realm: "host",
|
|
3688
|
+
reason: "static-dist-host-does-not-install-dev-capture"
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
};
|
|
3673
3693
|
} catch (cause) {
|
|
3674
3694
|
return { ok: false, error: commandError(cause, "preview-server-failed") };
|
|
3675
3695
|
}
|
|
@@ -3719,6 +3739,7 @@ var init_commands = __esm({
|
|
|
3719
3739
|
init_dist();
|
|
3720
3740
|
init_host();
|
|
3721
3741
|
init_project();
|
|
3742
|
+
init_types();
|
|
3722
3743
|
init_assets();
|
|
3723
3744
|
init_bootstrap_commands();
|
|
3724
3745
|
init_plugin_authoring();
|
|
@@ -4091,7 +4112,7 @@ function hasStaticToolDeclarations(entry) {
|
|
|
4091
4112
|
return Array.isArray(Reflect.get(entry.config, "tools"));
|
|
4092
4113
|
}
|
|
4093
4114
|
async function createViteModuleLoader(root) {
|
|
4094
|
-
const server = await createServer({
|
|
4115
|
+
const server = await createServer$1({
|
|
4095
4116
|
root,
|
|
4096
4117
|
appType: "custom",
|
|
4097
4118
|
configFile: false,
|
|
@@ -4293,7 +4314,7 @@ __export(contributions_exports, {
|
|
|
4293
4314
|
createBuildContribution: () => createBuildContribution,
|
|
4294
4315
|
createDefaultContributions: () => createDefaultContributions
|
|
4295
4316
|
});
|
|
4296
|
-
function
|
|
4317
|
+
function commandFailure2(error) {
|
|
4297
4318
|
return { ok: false, error: { ...error, detail: error.detail } };
|
|
4298
4319
|
}
|
|
4299
4320
|
function createBuildContribution(projectRoot2 = process.cwd()) {
|
|
@@ -4302,7 +4323,7 @@ function createBuildContribution(projectRoot2 = process.cwd()) {
|
|
|
4302
4323
|
async (options) => {
|
|
4303
4324
|
const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
4304
4325
|
const result = await buildCommand2({ ...options, root: options.root ?? projectRoot2 });
|
|
4305
|
-
return result.ok ? result.value :
|
|
4326
|
+
return result.ok ? result.value : commandFailure2(result.error);
|
|
4306
4327
|
}
|
|
4307
4328
|
);
|
|
4308
4329
|
}
|
|
@@ -4312,7 +4333,7 @@ function createAuthorContribution(projectRoot2 = process.cwd()) {
|
|
|
4312
4333
|
async (options) => {
|
|
4313
4334
|
const { pluginInstallCommand: pluginInstallCommand2 } = await Promise.resolve().then(() => (init_plugin_authoring(), plugin_authoring_exports));
|
|
4314
4335
|
const result = await pluginInstallCommand2({ ...options, root: options.root ?? projectRoot2 });
|
|
4315
|
-
return result.ok ? result.value :
|
|
4336
|
+
return result.ok ? result.value : commandFailure2(result.error);
|
|
4316
4337
|
}
|
|
4317
4338
|
);
|
|
4318
4339
|
}
|
|
@@ -4348,6 +4369,36 @@ var init_runtime = __esm({
|
|
|
4348
4369
|
}
|
|
4349
4370
|
});
|
|
4350
4371
|
|
|
4372
|
+
// src/cli-output.ts
|
|
4373
|
+
function agentOnboardingLines(value) {
|
|
4374
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
|
|
4375
|
+
const onboarding = value.onboarding;
|
|
4376
|
+
if (onboarding === null || typeof onboarding !== "object" || Array.isArray(onboarding)) return [];
|
|
4377
|
+
const candidate = onboarding;
|
|
4378
|
+
const lines = Array.isArray(candidate.read) ? candidate.read.filter((path) => typeof path === "string").map((path) => `[forgeax] read: ${path}`) : [];
|
|
4379
|
+
if (typeof candidate.next?.cwd === "string" && Array.isArray(candidate.next.argv) && candidate.next.argv.every((part) => typeof part === "string")) {
|
|
4380
|
+
lines.push(`[forgeax] next cwd: ${candidate.next.cwd}`);
|
|
4381
|
+
lines.push(`[forgeax] next: ${candidate.next.argv.join(" ")}`);
|
|
4382
|
+
}
|
|
4383
|
+
return lines;
|
|
4384
|
+
}
|
|
4385
|
+
function sdkUpdateLines(value) {
|
|
4386
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
|
|
4387
|
+
const update = value.sdkUpdate;
|
|
4388
|
+
if (update === null || typeof update !== "object" || Array.isArray(update)) return [];
|
|
4389
|
+
const candidate = update;
|
|
4390
|
+
if (candidate.status !== "available" || typeof candidate.currentVersion !== "string" || typeof candidate.latestVersion !== "string" || typeof candidate.migrationRisk !== "string") {
|
|
4391
|
+
return [];
|
|
4392
|
+
}
|
|
4393
|
+
return [
|
|
4394
|
+
`[forgeax] update available: @forgeax/engine-sdk ${candidate.currentVersion} -> ${candidate.latestVersion}`,
|
|
4395
|
+
`[forgeax] update note: ${candidate.migrationRisk}`
|
|
4396
|
+
];
|
|
4397
|
+
}
|
|
4398
|
+
function renderForgeaxUsage() {
|
|
4399
|
+
return "Usage: forgeax new [directory] [--template empty|game-default]\n forgeax <init|doctor|test|dev|build|package|serve|preview> [directory]\n forgeax package [directory] [--output release/game-web.zip]\n forgeax run <rhi.capture|rhi.summary|rhi.inspect> [options]\n forgeax run <operation-id> --input <request.json>\n forgeax asset <add|verify|inspect|list> [subject]\n forgeax shader check [path]\n forgeax plugin <install|uninstall> <module-or-id> [options]\n forgeax skill <install|verify> [--root directory]\n forgeax sdk install <directory> [--version VERSION]\n";
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4351
4402
|
// src/cli.ts
|
|
4352
4403
|
init_commands();
|
|
4353
4404
|
|
|
@@ -4547,13 +4598,16 @@ async function execCommand(options) {
|
|
|
4547
4598
|
}
|
|
4548
4599
|
|
|
4549
4600
|
// src/cli.ts
|
|
4550
|
-
|
|
4601
|
+
init_types();
|
|
4602
|
+
var rawArgs = process.argv.slice(2);
|
|
4603
|
+
var args = [...rawArgs];
|
|
4551
4604
|
var primary = args.shift();
|
|
4552
4605
|
var nested = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" ? args.shift() : void 0;
|
|
4553
4606
|
var command = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" ? `${primary}.${nested ?? ""}` : primary;
|
|
4554
4607
|
var json = args.includes("--json");
|
|
4555
4608
|
var dryRun = args.includes("--dry-run");
|
|
4556
4609
|
var noInstall = args.includes("--no-install");
|
|
4610
|
+
var help = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
4557
4611
|
function option(name) {
|
|
4558
4612
|
const index = args.indexOf(name);
|
|
4559
4613
|
return index < 0 ? void 0 : args[index + 1];
|
|
@@ -4570,6 +4624,7 @@ var optionValueIndexes = new Set(
|
|
|
4570
4624
|
"--input",
|
|
4571
4625
|
"--output",
|
|
4572
4626
|
"--root",
|
|
4627
|
+
"--port",
|
|
4573
4628
|
"--template",
|
|
4574
4629
|
"--version",
|
|
4575
4630
|
"--work-index"
|
|
@@ -4608,6 +4663,18 @@ var commands = [
|
|
|
4608
4663
|
"describe",
|
|
4609
4664
|
"exec"
|
|
4610
4665
|
];
|
|
4666
|
+
function projectServerOptions() {
|
|
4667
|
+
const parsedPort = parseProjectPortOption(option("--port"), args.includes("--port"));
|
|
4668
|
+
if (!parsedPort.ok) return parsedPort;
|
|
4669
|
+
return {
|
|
4670
|
+
ok: true,
|
|
4671
|
+
value: {
|
|
4672
|
+
root: projectRoot,
|
|
4673
|
+
json,
|
|
4674
|
+
...parsedPort.value === void 0 ? {} : { port: parsedPort.value }
|
|
4675
|
+
}
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4611
4678
|
async function run(value) {
|
|
4612
4679
|
switch (value) {
|
|
4613
4680
|
case "new": {
|
|
@@ -4624,8 +4691,10 @@ async function run(value) {
|
|
|
4624
4691
|
return doctorCommand({ root: projectRoot});
|
|
4625
4692
|
case "test":
|
|
4626
4693
|
return testCommand({ root: projectRoot});
|
|
4627
|
-
case "dev":
|
|
4628
|
-
|
|
4694
|
+
case "dev": {
|
|
4695
|
+
const options = projectServerOptions();
|
|
4696
|
+
return options.ok ? devCommand(options.value) : options;
|
|
4697
|
+
}
|
|
4629
4698
|
case "build": {
|
|
4630
4699
|
const base = option("--base");
|
|
4631
4700
|
const outDir = option("--out-dir");
|
|
@@ -4643,10 +4712,14 @@ async function run(value) {
|
|
|
4643
4712
|
...output === void 0 ? {} : { output }
|
|
4644
4713
|
});
|
|
4645
4714
|
}
|
|
4646
|
-
case "preview":
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4715
|
+
case "preview": {
|
|
4716
|
+
const options = projectServerOptions();
|
|
4717
|
+
return options.ok ? previewCommand(options.value) : options;
|
|
4718
|
+
}
|
|
4719
|
+
case "serve": {
|
|
4720
|
+
const options = projectServerOptions();
|
|
4721
|
+
return options.ok ? devCommand(options.value) : options;
|
|
4722
|
+
}
|
|
4650
4723
|
case "plugin.install": {
|
|
4651
4724
|
const module = positionals[0];
|
|
4652
4725
|
const id = option("--id");
|
|
@@ -4823,10 +4896,10 @@ async function run(value) {
|
|
|
4823
4896
|
}
|
|
4824
4897
|
}
|
|
4825
4898
|
}
|
|
4826
|
-
if (
|
|
4827
|
-
process.
|
|
4828
|
-
|
|
4829
|
-
);
|
|
4899
|
+
if (help && primary !== "run") {
|
|
4900
|
+
process.stdout.write(renderForgeaxUsage());
|
|
4901
|
+
} else if (command === void 0 || !commands.includes(command)) {
|
|
4902
|
+
process.stderr.write(renderForgeaxUsage());
|
|
4830
4903
|
process.exitCode = 2;
|
|
4831
4904
|
} else {
|
|
4832
4905
|
const stdoutWrite = process.stdout.write;
|
|
@@ -4846,9 +4919,23 @@ if (command === void 0 || !commands.includes(command)) {
|
|
|
4846
4919
|
const envelope = result.ok ? { schemaVersion: "1.0.0", command, ok: true, value: result.value } : { schemaVersion: "1.0.0", command, ok: false, error: result.error };
|
|
4847
4920
|
if (json) stdoutWrite.call(process.stdout, `${JSON.stringify(envelope)}
|
|
4848
4921
|
`);
|
|
4849
|
-
else if (result.ok)
|
|
4922
|
+
else if (result.ok) {
|
|
4923
|
+
process.stdout.write(`[forgeax] ${command} ready
|
|
4924
|
+
`);
|
|
4925
|
+
for (const line of agentOnboardingLines(result.value)) process.stdout.write(`${line}
|
|
4850
4926
|
`);
|
|
4851
|
-
|
|
4927
|
+
for (const line of sdkUpdateLines(result.value)) process.stdout.write(`${line}
|
|
4928
|
+
`);
|
|
4929
|
+
if (command === "sdk.install") {
|
|
4930
|
+
const value = result.value;
|
|
4931
|
+
if (typeof value.next?.cwd === "string" && Array.isArray(value.next.argv) && value.next.argv.every((part) => typeof part === "string")) {
|
|
4932
|
+
process.stdout.write(`[forgeax] next cwd: ${value.next.cwd}
|
|
4933
|
+
`);
|
|
4934
|
+
process.stdout.write(`[forgeax] next: ${value.next.argv.join(" ")}
|
|
4935
|
+
`);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
} else process.stderr.write(`[forgeax] ${result.error.code}: ${result.error.hint}
|
|
4852
4939
|
`);
|
|
4853
4940
|
if (!result.ok) process.exitCode = result.error.code === "cli-parse-error" ? 2 : 1;
|
|
4854
4941
|
}
|