@forgeax/engine-devkit 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -10,7 +10,10 @@ import { fbxImporter } from '@forgeax/engine-fbx';
10
10
  import { fontImporter } from '@forgeax/engine-font/font-importer';
11
11
  import { gltfImporter } from '@forgeax/engine-gltf';
12
12
  import { imageImporter } from '@forgeax/engine-image/image-importer';
13
+ import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
14
+ import { scanInventory } from '@forgeax/engine-pack/scanner';
13
15
  import { validatePreviewArtifactManifest, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution, describeResourcePreviewFailure, validateCanonicalKitReceipt } from '@forgeax/engine-preview';
16
+ import { createMaterialPackCooker } from '@forgeax/engine-shader-compiler';
14
17
  import { err, ok, createStandaloneRuntimeAssetBinding } from '@forgeax/engine-types';
15
18
  import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
16
19
  import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
@@ -20,9 +23,9 @@ import { runCliGltf } from '@forgeax/engine-gltf/cli-gltf';
20
23
  import { scanEntries } from '@forgeax/engine-pack/cli-asset';
21
24
  import { AssetGuid } from '@forgeax/engine-pack/guid';
22
25
  import { execFile } from 'child_process';
26
+ import { tmpdir } from 'os';
23
27
  import { promisify } from 'util';
24
28
  import { createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
25
- import { tmpdir } from 'os';
26
29
  import { build, createServer, preview } from 'vite';
27
30
  import { startVitest } from 'vitest/node';
28
31
  import materialPreviewPlugin from '@forgeax/engine-preview/material';
@@ -323,6 +326,42 @@ function isResourcePreviewIgnoredPath(path) {
323
326
  function isProjectSourceIgnoredPath(path) {
324
327
  return ignoreDevKitCatalogPath(path) || path.endsWith(".wgsl.meta.json");
325
328
  }
329
+ async function prepareBuiltinPack(projectRoots, generated, ignorePath) {
330
+ const inventory = await scanInventory(projectRoots, { ignorePath });
331
+ if (!inventory.ok) return void 0;
332
+ const declared = /* @__PURE__ */ new Set();
333
+ for (const declaration of inventory.value.declarations.values()) {
334
+ if (declaration.format === "pack.json") {
335
+ for (const asset of declaration.value.assets) declared.add(asset.guid.toLowerCase());
336
+ continue;
337
+ }
338
+ for (const asset of declaration.value.subAssets) declared.add(asset.guid.toLowerCase());
339
+ }
340
+ const missing = BUILTIN_MESH_ASSETS.filter((asset) => !declared.has(asset.guid.toLowerCase()));
341
+ if (missing.length === 0) return void 0;
342
+ const packPath = resolve(generated, "engine-builtins.pack.json");
343
+ await writeFile(
344
+ packPath,
345
+ `${JSON.stringify(
346
+ {
347
+ schemaVersion: "2.0.0",
348
+ kind: "internal-text-package",
349
+ assets: missing.map((asset) => ({
350
+ guid: asset.guid,
351
+ kind: "mesh",
352
+ payload: { geometry: asset.geometry },
353
+ refs: [],
354
+ artifacts: {}
355
+ }))
356
+ },
357
+ null,
358
+ 2
359
+ )}
360
+ `,
361
+ "utf8"
362
+ );
363
+ return packPath;
364
+ }
326
365
  function findEngineWorkspaceRoot() {
327
366
  let cursor = dirname(fileURLToPath(import.meta.url));
328
367
  for (; ; ) {
@@ -338,6 +377,7 @@ async function engineWorkspacePackages() {
338
377
  const workspaceRoot = findEngineWorkspaceRoot();
339
378
  if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
340
379
  const packageRoot = resolve(workspaceRoot, "packages");
380
+ if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
341
381
  const packages = /* @__PURE__ */ new Map();
342
382
  for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
343
383
  if (!entry.isDirectory()) continue;
@@ -432,6 +472,18 @@ async function createEngineWorkspaceResolver() {
432
472
  }
433
473
  };
434
474
  }
475
+ async function consumerEngineAliases(projectRoot) {
476
+ const root = resolve(projectRoot, "node_modules", ".pnpm", "node_modules", "@forgeax");
477
+ try {
478
+ const entries2 = await readdir(root, { withFileTypes: true });
479
+ return entries2.filter((entry) => entry.isDirectory() && entry.name.startsWith("engine-")).map((entry) => ({
480
+ find: `@forgeax/${entry.name}`,
481
+ replacement: resolve(root, entry.name)
482
+ })).sort((a, b) => a.find.localeCompare(b.find));
483
+ } catch {
484
+ return [];
485
+ }
486
+ }
435
487
  async function resolveCanonicalKit() {
436
488
  const packageJson = hostRequire.resolve("@forgeax/engine-preview/package.json");
437
489
  const root = resolve(packageJson, "..", "assets/canonical-kit");
@@ -506,6 +558,7 @@ function hostSource(facts, bootstrapRoot, canonicalEnvironmentGuid) {
506
558
  const plugins = bootstrapRoot === "resource-bootstrap" ? [] : [
507
559
  `webAudioPlugin()`,
508
560
  `audioPlugin()`,
561
+ `skinningPlugin()`,
509
562
  ...facts.physics === void 0 ? [] : [`physicsPlugin('${facts.physics === "2d" ? "rapier-2d" : "rapier-3d"}')`]
510
563
  ];
511
564
  const bootstrapPlugin = facts.bootstrapEntry === void 0 ? "" : `const bootstrapModule = await import(${JSON.stringify(
@@ -523,20 +576,21 @@ await app.pluginContext.plugin({
523
576
  },
524
577
  });`;
525
578
  return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
526
- import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine-app';
527
- import { createCatalogSource } from '@forgeax/engine-assets-runtime';
528
- import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine-plugin/loader';
529
- import { audioPlugin } from '@forgeax/engine-audio';
530
- import { webAudioPlugin } from '@forgeax/engine-audio-webaudio';
531
- import { physicsPlugin } from '@forgeax/engine-physics';
532
- import { createPrimitiveMesh } from '@forgeax/engine-geometry';
533
- import { createDevImportTransport } from '@forgeax/engine-runtime';
534
- import { mat4 } from '@forgeax/engine-math';
535
- import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine-render';
536
- import { projectSceneAsset, Transform, worldInstantiateScene } from '@forgeax/engine-scene';
537
- import { createStandaloneRuntimeAssetBinding } from '@forgeax/engine-types';
538
- import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine-vfx';
539
- import { createVfxRuntimeHost } from '@forgeax/engine-vfx-render';
579
+ import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
580
+ import { createCatalogSource } from '@forgeax/engine/assets-runtime';
581
+ import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
582
+ import { audioPlugin } from '@forgeax/engine/audio';
583
+ import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
584
+ import { physicsPlugin } from '@forgeax/engine/physics';
585
+ import { skinningPlugin } from '@forgeax/engine/skinning';
586
+ import { createPrimitiveMesh } from '@forgeax/engine/geometry';
587
+ import { createDevImportTransport } from '@forgeax/engine/runtime';
588
+ import { mat4 } from '@forgeax/engine/math';
589
+ import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
590
+ import { projectSceneAsset, Transform, worldInstantiateScene } from '@forgeax/engine/scene';
591
+ import { createStandaloneRuntimeAssetBinding } from '@forgeax/engine/types';
592
+ import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
593
+ import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
540
594
 
541
595
  const pluginCatalog = new Map([
542
596
  ${catalogModules(facts).map(
@@ -635,15 +689,16 @@ const resizeCanvas = () => {
635
689
  const resizeObserver = new ResizeObserver(resizeCanvas);
636
690
  resizeObserver.observe(canvas);
637
691
  resizeCanvas();
638
- const binding = import.meta.env.DEV
639
- ? createStandaloneRuntimeAssetBinding(${JSON.stringify(facts.id)})
640
- : undefined;
641
- const assetCatalog = binding === undefined
642
- ? undefined
643
- : createCatalogSource({ url: binding.catalogUrl, expectedScope: binding });
692
+ const runtimeScopeBinding = createStandaloneRuntimeAssetBinding(${JSON.stringify(facts.id)});
693
+ const assetCatalog = createCatalogSource({
694
+ url: import.meta.env.DEV
695
+ ? runtimeScopeBinding.catalogUrl
696
+ : new URL('pack-index.json', document.baseURI).href,
697
+ expectedScope: runtimeScopeBinding,
698
+ });
644
699
  const bundler = {
645
700
  ...forgeaxBundlerAdapter(),
646
- ...(binding === undefined ? {} : { importTransport: createDevImportTransport(binding) }),
701
+ ...(import.meta.env.DEV ? { importTransport: createDevImportTransport(runtimeScopeBinding) } : {}),
647
702
  };
648
703
 
649
704
  function previewStable(value) {
@@ -1022,12 +1077,18 @@ if (query.has('forgeax-tool-replay')) {
1022
1077
  window.addEventListener('pagehide', () => void host.value.dispose(), { once: true });
1023
1078
  }
1024
1079
  } else {
1080
+ // WebDriver/headless browsers cannot satisfy the trusted-gesture requirement
1081
+ // for pointer lock. Keep the game running and let its keyboard fallback own
1082
+ // camera input; real browser users retain the normal click-to-lock path.
1083
+ const pointerLockAllowed =
1084
+ typeof navigator !== 'undefined' && navigator.webdriver === true ? () => false : undefined;
1025
1085
  const result = await createApp(
1026
1086
  canvas,
1027
1087
  {
1028
1088
  ...(assetCatalog === undefined ? {} : { assetCatalog }),
1029
1089
  ...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
1030
1090
  plugins: [${plugins.join(", ")}],
1091
+ ...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
1031
1092
  },
1032
1093
  bundler,
1033
1094
  );
@@ -1086,10 +1147,32 @@ function htmlSource(title) {
1086
1147
  <div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
1087
1148
  <script>
1088
1149
  (() => {
1150
+ const formatStartupFailure = (reason) => {
1151
+ if (reason instanceof Error) return reason.message;
1152
+ if (reason !== null && typeof reason === 'object') {
1153
+ const record = reason;
1154
+ const lines = [];
1155
+ for (const key of ['code', 'expected', 'hint']) {
1156
+ if (typeof record[key] === 'string' && record[key].length > 0) {
1157
+ lines.push(key + ': ' + record[key]);
1158
+ }
1159
+ }
1160
+ const detail = record.detail;
1161
+ if (detail !== null && typeof detail === 'object') {
1162
+ for (const key of ['reason', 'guid']) {
1163
+ if (typeof detail[key] === 'string' && detail[key].length > 0) {
1164
+ lines.push('detail.' + key + ': ' + detail[key]);
1165
+ }
1166
+ }
1167
+ }
1168
+ if (lines.length > 0) return lines.join('\\n');
1169
+ }
1170
+ return String(reason ?? 'Unknown startup failure');
1171
+ };
1089
1172
  const show = (reason) => {
1090
1173
  const notice = document.querySelector('#forgeax-fatal');
1091
1174
  if (!(notice instanceof HTMLElement)) return;
1092
- const message = reason instanceof Error ? reason.message : String(reason ?? 'Unknown startup failure');
1175
+ const message = formatStartupFailure(reason);
1093
1176
  notice.textContent = 'ForgeaX game failed to start.\\n' + message;
1094
1177
  notice.style.display = 'grid';
1095
1178
  };
@@ -1104,29 +1187,32 @@ function htmlSource(title) {
1104
1187
  }
1105
1188
  async function createViteConfig(facts, command, base = "/", options = {}) {
1106
1189
  const bootstrapRoot = options.bootstrapRoot ?? "project-bootstrap";
1107
- const canonicalKit = bootstrapRoot === "resource-bootstrap" ? await resolveCanonicalKit() : void 0;
1190
+ const canonicalKit = await resolveCanonicalKit();
1108
1191
  const generated = resolve(facts.root, ".forgeax", "generated");
1109
1192
  await mkdir(generated, { recursive: true });
1193
+ const projectRoots = facts.assetRoots.map((root) => resolve(facts.root, root));
1194
+ const ignorePath = bootstrapRoot === "resource-bootstrap" ? isResourcePreviewIgnoredPath : isProjectSourceIgnoredPath;
1195
+ const builtinPack = await prepareBuiltinPack(projectRoots, generated, ignorePath);
1110
1196
  await Promise.all([
1111
1197
  writeFile(resolve(generated, "index.html"), htmlSource(facts.name)),
1112
1198
  writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
1113
1199
  ]);
1114
1200
  const roots = [
1115
- ...facts.assetRoots.map((root) => resolve(facts.root, root)),
1201
+ ...projectRoots,
1202
+ ...builtinPack === void 0 ? [] : [builtinPack],
1116
1203
  ...canonicalKit === void 0 ? [] : [canonicalKit.root]
1117
1204
  ];
1118
1205
  const importers = [...projectImporters(facts)];
1119
1206
  const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
1120
1207
  const engineWorkspaceResolver = await createEngineWorkspaceResolver();
1208
+ const consumerAliases = await consumerEngineAliases(facts.root);
1121
1209
  const plugins = [
1122
1210
  ...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
1123
1211
  forgeaxShader(),
1124
1212
  pluginPack({
1125
1213
  roots,
1126
- base,
1127
1214
  runtimeBinding,
1128
1215
  ddc: devKitDdcRoots(facts.root),
1129
- scriptablePack: {},
1130
1216
  refresh: command === "serve" ? reloadAssetHost() : void 0,
1131
1217
  importers: [
1132
1218
  audioImporter,
@@ -1136,8 +1222,8 @@ async function createViteConfig(facts, command, base = "/", options = {}) {
1136
1222
  fontImporter,
1137
1223
  ...importers
1138
1224
  ],
1139
- cookers: [createParticleCodeNativeCookerFromRoots(roots)],
1140
- ...bootstrapRoot === "resource-bootstrap" ? { ignorePath: isResourcePreviewIgnoredPath } : { ignorePath: isProjectSourceIgnoredPath }
1225
+ cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
1226
+ ignorePath
1141
1227
  })
1142
1228
  ];
1143
1229
  return {
@@ -1146,7 +1232,10 @@ async function createViteConfig(facts, command, base = "/", options = {}) {
1146
1232
  configFile: false,
1147
1233
  publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
1148
1234
  plugins,
1149
- resolve: { dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"] },
1235
+ resolve: {
1236
+ alias: consumerAliases,
1237
+ dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"]
1238
+ },
1150
1239
  server: { fs: { allow: [facts.root, ...roots] } },
1151
1240
  build: {
1152
1241
  target: "esnext",
@@ -1698,6 +1787,7 @@ function createInitPlan(facts, sdk) {
1698
1787
  value: {
1699
1788
  root: facts.root,
1700
1789
  version: sdk?.sdkVersion ?? devkitVersion,
1790
+ ...sdk === void 0 ? {} : { pnpmVersion: sdk.requirements.pnpm },
1701
1791
  archiveBacked: sdk !== void 0,
1702
1792
  dependencyChanges,
1703
1793
  scriptChanges
@@ -1718,23 +1808,8 @@ async function applyInitPlan(facts, plan, options) {
1718
1808
  for (const change of plan.scriptChanges) scripts[change.name] = change.to;
1719
1809
  manifest.scripts = scripts;
1720
1810
  if (plan.archiveBacked) {
1721
- manifest.packageManager = "pnpm@10.33.2";
1722
- const pnpmValue = manifest.pnpm;
1723
- const pnpm = pnpmValue !== null && typeof pnpmValue === "object" ? pnpmValue : {};
1724
- manifest.pnpm = {
1725
- ...pnpm,
1726
- onlyBuiltDependencies: [
1727
- "@forgeax/engine-codec",
1728
- "@forgeax/engine-fbx",
1729
- "@forgeax/engine-wgpu-wasm",
1730
- "esbuild"
1731
- ],
1732
- supportedArchitectures: {
1733
- os: ["darwin", "linux", "win32"],
1734
- cpu: ["x64", "arm64"],
1735
- libc: ["glibc", "musl"]
1736
- }
1737
- };
1811
+ if (plan.pnpmVersion === void 0) throw new Error("sdk-pnpm-version-missing");
1812
+ manifest.packageManager = `pnpm@${plan.pnpmVersion}`;
1738
1813
  }
1739
1814
  await writeFile(resolve(facts.root, "package.json"), `${JSON.stringify(manifest, null, 2)}
1740
1815
  `);
@@ -1800,6 +1875,154 @@ var init_sdk = __esm({
1800
1875
  "src/sdk.ts"() {
1801
1876
  }
1802
1877
  });
1878
+ function sdkInitPath(sdk) {
1879
+ return resolve(sdk.root, ...SDK_INIT_PATH);
1880
+ }
1881
+ function commandFailure(code, expected, hint, detail = {}) {
1882
+ return { ok: false, error: { code, expected, hint, detail } };
1883
+ }
1884
+ function sdkProjectInstallArgs(store) {
1885
+ const common = [
1886
+ "install",
1887
+ "--frozen-lockfile",
1888
+ "--ignore-scripts",
1889
+ "--side-effects-cache=true",
1890
+ "--child-concurrency=1"
1891
+ ];
1892
+ return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1893
+ }
1894
+ function sdkBootstrapInstallArgs(store) {
1895
+ const common = [
1896
+ "install",
1897
+ "--frozen-lockfile",
1898
+ "--child-concurrency=1",
1899
+ "--side-effects-cache=true"
1900
+ ];
1901
+ return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1902
+ }
1903
+ function supportedPnpm(version) {
1904
+ const [major = 0, minor = 0] = version.split(".").map(Number);
1905
+ return major === 11 && minor >= 7;
1906
+ }
1907
+ async function currentPnpm() {
1908
+ try {
1909
+ const result = await execFileAsync("pnpm", ["--version"], { maxBuffer: 1024 * 1024 });
1910
+ const version = result.stdout.trim();
1911
+ if (version.length === 0) throw new Error("pnpm returned an empty version");
1912
+ return { ok: true, value: version };
1913
+ } catch (cause) {
1914
+ return commandFailure(
1915
+ "pnpm-unavailable",
1916
+ "pnpm 11.7.0 or newer in the pnpm 11 line to be available on PATH",
1917
+ "Install or activate pnpm 11, then rerun forgeax init from the SDK root.",
1918
+ { reason: cause instanceof Error ? cause.message : String(cause) }
1919
+ );
1920
+ }
1921
+ }
1922
+ function matchesState(sdk, state) {
1923
+ if (state === null || typeof state !== "object" || Array.isArray(state)) return false;
1924
+ const value = state;
1925
+ 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;
1926
+ }
1927
+ async function readSdkInitState(sdk) {
1928
+ try {
1929
+ const value = JSON.parse(await readFile(sdkInitPath(sdk), "utf8"));
1930
+ return matchesState(sdk, value) ? value : void 0;
1931
+ } catch {
1932
+ return void 0;
1933
+ }
1934
+ }
1935
+ async function requireSdkInitialization(sdk) {
1936
+ const state = await readSdkInitState(sdk);
1937
+ if (state === void 0) {
1938
+ return commandFailure(
1939
+ "sdk-not-initialized",
1940
+ "the downloaded SDK to be initialized for this Node/pnpm/platform tuple",
1941
+ "Run ./bin/forgeax init from the SDK root once, then run forgeax new outside the SDK.",
1942
+ {
1943
+ sdkRoot: sdk.root,
1944
+ sdkVersion: sdk.manifest.sdkVersion,
1945
+ pnpm: sdk.manifest.requirements.pnpm,
1946
+ node: process.versions.node,
1947
+ platform: process.platform,
1948
+ arch: process.arch
1949
+ }
1950
+ );
1951
+ }
1952
+ return { ok: true, value: state };
1953
+ }
1954
+ async function copyBootstrapInputs(sdk, root) {
1955
+ const template = sdk.templates.get(sdk.defaultTemplate);
1956
+ if (template === void 0) throw new Error("sdk-default-template-missing");
1957
+ for (const name of ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"]) {
1958
+ await cp(resolve(template, name), resolve(root, name));
1959
+ }
1960
+ try {
1961
+ await cp(resolve(template, ".npmrc"), resolve(root, ".npmrc"));
1962
+ } catch (cause) {
1963
+ if (cause === null || typeof cause !== "object" || !("code" in cause) || cause.code !== "ENOENT") {
1964
+ throw cause;
1965
+ }
1966
+ }
1967
+ }
1968
+ async function sdkInitCommand(sdk, options = {}) {
1969
+ const pnpmResult = await currentPnpm();
1970
+ if (!pnpmResult.ok) return pnpmResult;
1971
+ if (!supportedPnpm(pnpmResult.value)) {
1972
+ return commandFailure(
1973
+ "pnpm-version-unsupported",
1974
+ "pnpm >=11.7.0 <12",
1975
+ "Activate pnpm 11.7.0 or newer in the pnpm 11 line, then rerun forgeax init.",
1976
+ { actual: pnpmResult.value, expected: sdk.manifest.requirements.pnpm }
1977
+ );
1978
+ }
1979
+ const state = {
1980
+ schemaVersion: SDK_INIT_SCHEMA_VERSION,
1981
+ sdkVersion: sdk.manifest.sdkVersion,
1982
+ engineCommit: sdk.manifest.engineCommit,
1983
+ pnpm: pnpmResult.value,
1984
+ node: process.versions.node,
1985
+ platform: process.platform,
1986
+ arch: process.arch
1987
+ };
1988
+ const report = {
1989
+ ...state,
1990
+ root: sdk.root,
1991
+ store: sdk.store === void 0 ? "registry" : "offline"
1992
+ };
1993
+ if (options.dryRun === true || options.install === false) return { ok: true, value: report };
1994
+ let staging;
1995
+ try {
1996
+ staging = await mkdtemp(resolve(tmpdir(), "forgeax-sdk-init-"));
1997
+ await copyBootstrapInputs(sdk, staging);
1998
+ await execFileAsync("pnpm", sdkBootstrapInstallArgs(sdk.store), {
1999
+ cwd: staging,
2000
+ env: { ...process.env, CI: "true" },
2001
+ maxBuffer: 16 * 1024 * 1024
2002
+ });
2003
+ await mkdir(resolve(sdk.root, ".forgeax"), { recursive: true });
2004
+ await writeFile(sdkInitPath(sdk), `${JSON.stringify(state, null, 2)}
2005
+ `);
2006
+ return { ok: true, value: report };
2007
+ } catch (cause) {
2008
+ return commandFailure(
2009
+ "sdk-init-failed",
2010
+ "the SDK dependency closure to install and build native packages successfully",
2011
+ "Inspect the pnpm output, repair Node/pnpm or platform permissions, then rerun forgeax init from the SDK root.",
2012
+ { reason: cause instanceof Error ? cause.message : String(cause), sdkRoot: sdk.root }
2013
+ );
2014
+ } finally {
2015
+ if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2016
+ }
2017
+ }
2018
+ var execFileAsync, SDK_INIT_PATH, SDK_INIT_SCHEMA_VERSION;
2019
+ var init_sdk_bootstrap = __esm({
2020
+ "src/sdk-bootstrap.ts"() {
2021
+ execFileAsync = promisify(execFile);
2022
+ SDK_INIT_PATH = [".forgeax", "sdk-init.json"];
2023
+ SDK_INIT_SCHEMA_VERSION = "1.0.0";
2024
+ }
2025
+ });
1803
2026
  function slash(path) {
1804
2027
  return path.split(sep).join("/");
1805
2028
  }
@@ -2165,6 +2388,20 @@ async function canonicalProspectivePath(path) {
2165
2388
  }
2166
2389
  }
2167
2390
  }
2391
+ async function configureSdkStore(root, store) {
2392
+ if (store === void 0) return;
2393
+ const path = resolve(root, ".npmrc");
2394
+ let content = "";
2395
+ try {
2396
+ content = await readFile(path, "utf8");
2397
+ } catch (cause) {
2398
+ if (!isMissingPathError(cause)) throw cause;
2399
+ }
2400
+ const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*store-dir\s*=/.test(line));
2401
+ lines.push(`store-dir=${store}`);
2402
+ await writeFile(path, `${lines.join("\n")}
2403
+ `);
2404
+ }
2168
2405
  function containsOrEquals(parent, child) {
2169
2406
  const path = relative(parent, child);
2170
2407
  return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
@@ -2175,25 +2412,14 @@ function nodeSupported() {
2175
2412
  }
2176
2413
  function pnpmSupported(version) {
2177
2414
  const [major = 0, minor = 0] = version.split(".").map(Number);
2178
- return major === 10 && minor >= 33;
2179
- }
2180
- function sdkInstallArgs(store) {
2181
- if (store === void 0) return ["install", "--frozen-lockfile"];
2182
- return [
2183
- "install",
2184
- "--offline",
2185
- "--frozen-lockfile",
2186
- "--side-effects-cache=false",
2187
- "--store-dir",
2188
- store
2189
- ];
2415
+ return major === 11 && minor >= 7;
2190
2416
  }
2191
2417
  async function doctorCommand(options = {}) {
2192
2418
  const facts = await readProjectFacts(options.root);
2193
2419
  if (!facts.ok) return facts;
2194
2420
  let pnpm;
2195
2421
  try {
2196
- pnpm = (await execFileAsync("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2422
+ pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2197
2423
  } catch (cause) {
2198
2424
  return {
2199
2425
  ok: false,
@@ -2221,7 +2447,7 @@ async function doctorCommand(options = {}) {
2221
2447
  ok: false,
2222
2448
  error: {
2223
2449
  code: "pnpm-version-unsupported",
2224
- expected: "pnpm >=10.33.0 <11",
2450
+ expected: "pnpm >=11.7.0 <12",
2225
2451
  hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
2226
2452
  detail: { actual: pnpm }
2227
2453
  }
@@ -2260,12 +2486,20 @@ async function doctorCommand(options = {}) {
2260
2486
  };
2261
2487
  }
2262
2488
  async function initCommand(options = {}) {
2263
- const facts = await readProjectFacts(options.root);
2264
- if (!facts.ok) return facts;
2265
2489
  try {
2266
2490
  const sdk = await findSdkContext();
2491
+ const root = await canonicalProspectivePath(options.root ?? process.cwd());
2492
+ if (sdk !== void 0 && root === await canonicalProspectivePath(sdk.root)) {
2493
+ return sdkInitCommand(sdk, options);
2494
+ }
2495
+ const facts = await readProjectFacts(root);
2496
+ if (!facts.ok) return facts;
2267
2497
  const plan = createInitPlan(facts.value, sdk?.manifest);
2268
2498
  if (!plan.ok) return plan;
2499
+ if (sdk !== void 0 && options.dryRun !== true) {
2500
+ const initialized = await requireSdkInitialization(sdk);
2501
+ if (!initialized.ok) return initialized;
2502
+ }
2269
2503
  const applied = await applyInitPlan(facts.value, plan.value, options);
2270
2504
  if (!applied.ok || options.dryRun === true) return applied;
2271
2505
  if (sdk !== void 0) {
@@ -2275,12 +2509,31 @@ async function initCommand(options = {}) {
2275
2509
  resolve(template, "pnpm-lock.yaml"),
2276
2510
  resolve(facts.value.root, "pnpm-lock.yaml")
2277
2511
  );
2512
+ await copyFile(
2513
+ resolve(template, "pnpm-workspace.yaml"),
2514
+ resolve(facts.value.root, "pnpm-workspace.yaml")
2515
+ );
2516
+ try {
2517
+ await readFile(resolve(facts.value.root, ".npmrc"), "utf8");
2518
+ } catch (cause) {
2519
+ if (!isMissingPathError(cause)) throw cause;
2520
+ try {
2521
+ await copyFile(resolve(template, ".npmrc"), resolve(facts.value.root, ".npmrc"));
2522
+ } catch (templateCause) {
2523
+ if (!isMissingPathError(templateCause)) throw templateCause;
2524
+ }
2525
+ }
2278
2526
  await copySdkSkills(sdk, facts.value.root);
2279
2527
  await installProjectSkills(facts.value.root, sdk.manifest);
2528
+ await configureSdkStore(
2529
+ facts.value.root,
2530
+ sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store)
2531
+ );
2280
2532
  }
2281
2533
  if (options.install === false) return applied;
2282
- const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkInstallArgs(sdk.store);
2283
- await execFileAsync("pnpm", installArgs, {
2534
+ const store = sdk === void 0 || sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2535
+ const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkProjectInstallArgs(store);
2536
+ await execFileAsync2("pnpm", installArgs, {
2284
2537
  cwd: facts.value.root,
2285
2538
  env: { ...process.env, CI: "true" },
2286
2539
  maxBuffer: 16 * 1024 * 1024
@@ -2358,12 +2611,15 @@ async function newCommand(options = {}) {
2358
2611
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2359
2612
  };
2360
2613
  }
2614
+ const initialized = await requireSdkInitialization(sdk);
2615
+ if (!initialized.ok) return initialized;
2361
2616
  const parent = dirname(root);
2362
2617
  await mkdir(parent, { recursive: true });
2363
2618
  let staging = await mkdtemp(
2364
2619
  resolve(parent, `.${basename(root)}.forgeax-staging-`)
2365
2620
  );
2366
2621
  const committedNames = [];
2622
+ let committed = false;
2367
2623
  try {
2368
2624
  for (const name of await readdir(template)) {
2369
2625
  await cp(resolve(template, name), resolve(staging, name), {
@@ -2374,14 +2630,12 @@ async function newCommand(options = {}) {
2374
2630
  }
2375
2631
  await copySdkSkills(sdk, staging);
2376
2632
  await installProjectSkills(staging, sdk.manifest);
2377
- await execFileAsync("pnpm", sdkInstallArgs(sdk.store), {
2378
- cwd: staging,
2379
- env: { ...process.env, CI: "true" },
2380
- maxBuffer: 16 * 1024 * 1024
2381
- });
2633
+ const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2634
+ await configureSdkStore(staging, store);
2382
2635
  if (!targetExists) {
2383
2636
  await rename(staging, root);
2384
2637
  staging = void 0;
2638
+ committedNames.push(...await readdir(root));
2385
2639
  } else {
2386
2640
  for (const name of await readdir(staging)) {
2387
2641
  await rename(resolve(staging, name), resolve(root, name));
@@ -2390,29 +2644,49 @@ async function newCommand(options = {}) {
2390
2644
  await rm(staging, { recursive: true, force: true });
2391
2645
  staging = void 0;
2392
2646
  }
2647
+ committed = true;
2648
+ await execFileAsync2("pnpm", sdkProjectInstallArgs(store), {
2649
+ cwd: root,
2650
+ env: { ...process.env, CI: "true" },
2651
+ maxBuffer: 16 * 1024 * 1024
2652
+ });
2393
2653
  return {
2394
2654
  ok: true,
2395
2655
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2396
2656
  };
2397
2657
  } catch (cause) {
2398
2658
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2399
- await Promise.all(
2400
- committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2401
- );
2659
+ if (committed) {
2660
+ if (!targetExists) {
2661
+ await rm(root, { recursive: true, force: true });
2662
+ return { ok: false, error: commandError(cause, "project-create-failed") };
2663
+ }
2664
+ const cleanupNames = /* @__PURE__ */ new Set([...committedNames, "node_modules"]);
2665
+ await Promise.all(
2666
+ [...cleanupNames].map(
2667
+ (name) => rm(resolve(root, name), { recursive: true, force: true })
2668
+ )
2669
+ );
2670
+ } else {
2671
+ await Promise.all(
2672
+ committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2673
+ );
2674
+ }
2402
2675
  return { ok: false, error: commandError(cause, "project-create-failed") };
2403
2676
  }
2404
2677
  } catch (cause) {
2405
2678
  return { ok: false, error: commandError(cause, "project-create-failed") };
2406
2679
  }
2407
2680
  }
2408
- var execFileAsync;
2681
+ var execFileAsync2;
2409
2682
  var init_bootstrap_commands = __esm({
2410
2683
  "src/bootstrap-commands.ts"() {
2411
2684
  init_init();
2412
2685
  init_project();
2413
2686
  init_sdk();
2687
+ init_sdk_bootstrap();
2414
2688
  init_skill_install();
2415
- execFileAsync = promisify(execFile);
2689
+ execFileAsync2 = promisify(execFile);
2416
2690
  }
2417
2691
  });
2418
2692
 
@@ -2471,7 +2745,7 @@ function withoutEntry(entries2, id) {
2471
2745
  }
2472
2746
  async function mutateDependency(root, action, dependency) {
2473
2747
  if (dependency === void 0) return;
2474
- await execFileAsync2("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2748
+ await execFileAsync3("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2475
2749
  }
2476
2750
  async function pluginInstallCommand(options) {
2477
2751
  const root = resolve(options.root ?? process.cwd());
@@ -2573,10 +2847,10 @@ async function pluginUninstallCommand(options) {
2573
2847
  };
2574
2848
  }
2575
2849
  }
2576
- var execFileAsync2;
2850
+ var execFileAsync3;
2577
2851
  var init_plugin_authoring = __esm({
2578
2852
  "src/plugin-authoring.ts"() {
2579
- execFileAsync2 = promisify(execFile);
2853
+ execFileAsync3 = promisify(execFile);
2580
2854
  }
2581
2855
  });
2582
2856
  var RhiError;
@@ -3431,7 +3705,7 @@ async function sdkInstallCommand(options) {
3431
3705
  try {
3432
3706
  staging = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-staging-`));
3433
3707
  download = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-download-`));
3434
- await execFileAsync3(
3708
+ await execFileAsync4(
3435
3709
  process.env.FORGEAX_NPM_CLIENT ?? "npm",
3436
3710
  [
3437
3711
  "install",
@@ -3472,11 +3746,11 @@ async function sdkInstallCommand(options) {
3472
3746
  if (download !== void 0) await rm(download, { recursive: true, force: true });
3473
3747
  }
3474
3748
  }
3475
- var execFileAsync3;
3749
+ var execFileAsync4;
3476
3750
  var init_sdk_install = __esm({
3477
3751
  "src/sdk-install.ts"() {
3478
3752
  init_project();
3479
- execFileAsync3 = promisify(execFile);
3753
+ execFileAsync4 = promisify(execFile);
3480
3754
  }
3481
3755
  });
3482
3756
  async function shaderCheckCommand(options = {}) {
@@ -4319,7 +4593,7 @@ __export(contributions_exports, {
4319
4593
  createBuildContribution: () => createBuildContribution,
4320
4594
  createDefaultContributions: () => createDefaultContributions
4321
4595
  });
4322
- function commandFailure(error) {
4596
+ function commandFailure2(error) {
4323
4597
  return { ok: false, error: { ...error, detail: error.detail } };
4324
4598
  }
4325
4599
  function createBuildContribution(projectRoot = process.cwd()) {
@@ -4328,7 +4602,7 @@ function createBuildContribution(projectRoot = process.cwd()) {
4328
4602
  async (options) => {
4329
4603
  const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
4330
4604
  const result = await buildCommand2({ ...options, root: options.root ?? projectRoot });
4331
- return result.ok ? result.value : commandFailure(result.error);
4605
+ return result.ok ? result.value : commandFailure2(result.error);
4332
4606
  }
4333
4607
  );
4334
4608
  }
@@ -4338,7 +4612,7 @@ function createAuthorContribution(projectRoot = process.cwd()) {
4338
4612
  async (options) => {
4339
4613
  const { pluginInstallCommand: pluginInstallCommand2 } = await Promise.resolve().then(() => (init_plugin_authoring(), plugin_authoring_exports));
4340
4614
  const result = await pluginInstallCommand2({ ...options, root: options.root ?? projectRoot });
4341
- return result.ok ? result.value : commandFailure(result.error);
4615
+ return result.ok ? result.value : commandFailure2(result.error);
4342
4616
  }
4343
4617
  );
4344
4618
  }
@@ -4727,14 +5001,14 @@ async function createCarrierProviderService(options) {
4727
5001
  );
4728
5002
  }
4729
5003
  });
4730
- await new Promise((resolve15, reject) => {
5004
+ await new Promise((resolve16, reject) => {
4731
5005
  const onError = (error) => {
4732
5006
  server.off("listening", onListening);
4733
5007
  reject(error);
4734
5008
  };
4735
5009
  const onListening = () => {
4736
5010
  server.off("error", onError);
4737
- resolve15();
5011
+ resolve16();
4738
5012
  };
4739
5013
  server.once("error", onError);
4740
5014
  server.once("listening", onListening);
@@ -4752,7 +5026,7 @@ async function createCarrierProviderService(options) {
4752
5026
  if (closed) return;
4753
5027
  closed = true;
4754
5028
  await new Promise(
4755
- (resolve15, reject) => server.close((error) => error === void 0 ? resolve15() : reject(error))
5029
+ (resolve16, reject) => server.close((error) => error === void 0 ? resolve16() : reject(error))
4756
5030
  );
4757
5031
  }
4758
5032
  };
@@ -5046,14 +5320,14 @@ function projectUri(projectRoot, path) {
5046
5320
  async function allocateLoopbackPort() {
5047
5321
  const probe = createServer$2();
5048
5322
  try {
5049
- await new Promise((resolve15, reject) => {
5323
+ await new Promise((resolve16, reject) => {
5050
5324
  const onError = (error) => {
5051
5325
  probe.off("listening", onListening);
5052
5326
  reject(error);
5053
5327
  };
5054
5328
  const onListening = () => {
5055
5329
  probe.off("error", onError);
5056
- resolve15();
5330
+ resolve16();
5057
5331
  };
5058
5332
  probe.once("error", onError);
5059
5333
  probe.once("listening", onListening);
@@ -5066,7 +5340,7 @@ async function allocateLoopbackPort() {
5066
5340
  return address.port;
5067
5341
  } finally {
5068
5342
  if (probe.listening) {
5069
- await new Promise((resolve15) => probe.close(() => resolve15()));
5343
+ await new Promise((resolve16) => probe.close(() => resolve16()));
5070
5344
  }
5071
5345
  }
5072
5346
  }
@@ -5713,14 +5987,14 @@ async function createAuthenticatedLoopbackService(options) {
5713
5987
  reply2(response, 400, { error: message });
5714
5988
  }
5715
5989
  });
5716
- await new Promise((resolve15, reject) => {
5990
+ await new Promise((resolve16, reject) => {
5717
5991
  const onError = (error) => {
5718
5992
  server.off("listening", onListening);
5719
5993
  reject(error);
5720
5994
  };
5721
5995
  const onListening = () => {
5722
5996
  server.off("error", onError);
5723
- resolve15();
5997
+ resolve16();
5724
5998
  };
5725
5999
  server.once("error", onError);
5726
6000
  server.once("listening", onListening);
@@ -5728,7 +6002,7 @@ async function createAuthenticatedLoopbackService(options) {
5728
6002
  });
5729
6003
  const address = server.address();
5730
6004
  if (address === null || typeof address === "string") {
5731
- await new Promise((resolve15) => server.close(() => resolve15()));
6005
+ await new Promise((resolve16) => server.close(() => resolve16()));
5732
6006
  throw new Error("loopback service did not expose a TCP address");
5733
6007
  }
5734
6008
  const endpoint = `http://${host}:${address.port}/run`;
@@ -5743,8 +6017,8 @@ async function createAuthenticatedLoopbackService(options) {
5743
6017
  async close() {
5744
6018
  if (closed) return;
5745
6019
  closed = true;
5746
- await new Promise((resolve15, reject) => {
5747
- server.close((error) => error === void 0 ? resolve15() : reject(error));
6020
+ await new Promise((resolve16, reject) => {
6021
+ server.close((error) => error === void 0 ? resolve16() : reject(error));
5748
6022
  });
5749
6023
  transport.close();
5750
6024
  }