@forgeax/engine-devkit 0.1.2 → 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/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,9 +24,9 @@ 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 { tmpdir } from 'os';
27
30
  import { build, createServer, preview } from 'vite';
28
31
  import { startVitest } from 'vitest/node';
29
32
  import materialPreviewPlugin from '@forgeax/engine-preview/material';
@@ -319,6 +322,42 @@ function isResourcePreviewIgnoredPath(path) {
319
322
  function isProjectSourceIgnoredPath(path) {
320
323
  return ignoreDevKitCatalogPath(path) || path.endsWith(".wgsl.meta.json");
321
324
  }
325
+ async function prepareBuiltinPack(projectRoots, generated, ignorePath) {
326
+ const inventory = await scanInventory(projectRoots, { ignorePath });
327
+ if (!inventory.ok) return void 0;
328
+ const declared = /* @__PURE__ */ new Set();
329
+ for (const declaration of inventory.value.declarations.values()) {
330
+ if (declaration.format === "pack.json") {
331
+ for (const asset of declaration.value.assets) declared.add(asset.guid.toLowerCase());
332
+ continue;
333
+ }
334
+ for (const asset of declaration.value.subAssets) declared.add(asset.guid.toLowerCase());
335
+ }
336
+ const missing = BUILTIN_MESH_ASSETS.filter((asset) => !declared.has(asset.guid.toLowerCase()));
337
+ if (missing.length === 0) return void 0;
338
+ const packPath = resolve(generated, "engine-builtins.pack.json");
339
+ await writeFile(
340
+ packPath,
341
+ `${JSON.stringify(
342
+ {
343
+ schemaVersion: "2.0.0",
344
+ kind: "internal-text-package",
345
+ assets: missing.map((asset) => ({
346
+ guid: asset.guid,
347
+ kind: "mesh",
348
+ payload: { geometry: asset.geometry },
349
+ refs: [],
350
+ artifacts: {}
351
+ }))
352
+ },
353
+ null,
354
+ 2
355
+ )}
356
+ `,
357
+ "utf8"
358
+ );
359
+ return packPath;
360
+ }
322
361
  function findEngineWorkspaceRoot() {
323
362
  let cursor = dirname(fileURLToPath(import.meta.url));
324
363
  for (; ; ) {
@@ -334,6 +373,7 @@ async function engineWorkspacePackages() {
334
373
  const workspaceRoot = findEngineWorkspaceRoot();
335
374
  if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
336
375
  const packageRoot = resolve(workspaceRoot, "packages");
376
+ if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
337
377
  const packages = /* @__PURE__ */ new Map();
338
378
  for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
339
379
  if (!entry.isDirectory()) continue;
@@ -428,6 +468,18 @@ async function createEngineWorkspaceResolver() {
428
468
  }
429
469
  };
430
470
  }
471
+ async function consumerEngineAliases(projectRoot2) {
472
+ const root = resolve(projectRoot2, "node_modules", ".pnpm", "node_modules", "@forgeax");
473
+ try {
474
+ const entries2 = await readdir(root, { withFileTypes: true });
475
+ return entries2.filter((entry) => entry.isDirectory() && entry.name.startsWith("engine-")).map((entry) => ({
476
+ find: `@forgeax/${entry.name}`,
477
+ replacement: resolve(root, entry.name)
478
+ })).sort((a, b) => a.find.localeCompare(b.find));
479
+ } catch {
480
+ return [];
481
+ }
482
+ }
431
483
  async function resolveCanonicalKit() {
432
484
  const packageJson = hostRequire.resolve("@forgeax/engine-preview/package.json");
433
485
  const root = resolve(packageJson, "..", "assets/canonical-kit");
@@ -502,6 +554,7 @@ function hostSource(facts, bootstrapRoot, canonicalEnvironmentGuid) {
502
554
  const plugins = bootstrapRoot === "resource-bootstrap" ? [] : [
503
555
  `webAudioPlugin()`,
504
556
  `audioPlugin()`,
557
+ `skinningPlugin()`,
505
558
  ...facts.physics === void 0 ? [] : [`physicsPlugin('${facts.physics === "2d" ? "rapier-2d" : "rapier-3d"}')`]
506
559
  ];
507
560
  const bootstrapPlugin = facts.bootstrapEntry === void 0 ? "" : `const bootstrapModule = await import(${JSON.stringify(
@@ -519,20 +572,21 @@ await app.pluginContext.plugin({
519
572
  },
520
573
  });`;
521
574
  return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
522
- import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine-app';
523
- import { createCatalogSource } from '@forgeax/engine-assets-runtime';
524
- import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine-plugin/loader';
525
- import { audioPlugin } from '@forgeax/engine-audio';
526
- import { webAudioPlugin } from '@forgeax/engine-audio-webaudio';
527
- import { physicsPlugin } from '@forgeax/engine-physics';
528
- import { createPrimitiveMesh } from '@forgeax/engine-geometry';
529
- import { createDevImportTransport } from '@forgeax/engine-runtime';
530
- import { mat4 } from '@forgeax/engine-math';
531
- import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine-render';
532
- import { projectSceneAsset, Transform, worldInstantiateScene } from '@forgeax/engine-scene';
533
- import { createStandaloneRuntimeAssetBinding } from '@forgeax/engine-types';
534
- import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine-vfx';
535
- import { createVfxRuntimeHost } from '@forgeax/engine-vfx-render';
575
+ import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
576
+ import { createCatalogSource } from '@forgeax/engine/assets-runtime';
577
+ import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
578
+ import { audioPlugin } from '@forgeax/engine/audio';
579
+ import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
580
+ import { physicsPlugin } from '@forgeax/engine/physics';
581
+ import { skinningPlugin } from '@forgeax/engine/skinning';
582
+ import { createPrimitiveMesh } from '@forgeax/engine/geometry';
583
+ import { createDevImportTransport } from '@forgeax/engine/runtime';
584
+ import { mat4 } from '@forgeax/engine/math';
585
+ import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
586
+ import { projectSceneAsset, Transform, worldInstantiateScene } from '@forgeax/engine/scene';
587
+ import { createStandaloneRuntimeAssetBinding } from '@forgeax/engine/types';
588
+ import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
589
+ import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
536
590
 
537
591
  const pluginCatalog = new Map([
538
592
  ${catalogModules(facts).map(
@@ -631,15 +685,16 @@ const resizeCanvas = () => {
631
685
  const resizeObserver = new ResizeObserver(resizeCanvas);
632
686
  resizeObserver.observe(canvas);
633
687
  resizeCanvas();
634
- const binding = import.meta.env.DEV
635
- ? createStandaloneRuntimeAssetBinding(${JSON.stringify(facts.id)})
636
- : undefined;
637
- const assetCatalog = binding === undefined
638
- ? undefined
639
- : createCatalogSource({ url: binding.catalogUrl, expectedScope: binding });
688
+ const runtimeScopeBinding = createStandaloneRuntimeAssetBinding(${JSON.stringify(facts.id)});
689
+ const assetCatalog = createCatalogSource({
690
+ url: import.meta.env.DEV
691
+ ? runtimeScopeBinding.catalogUrl
692
+ : new URL('pack-index.json', document.baseURI).href,
693
+ expectedScope: runtimeScopeBinding,
694
+ });
640
695
  const bundler = {
641
696
  ...forgeaxBundlerAdapter(),
642
- ...(binding === undefined ? {} : { importTransport: createDevImportTransport(binding) }),
697
+ ...(import.meta.env.DEV ? { importTransport: createDevImportTransport(runtimeScopeBinding) } : {}),
643
698
  };
644
699
 
645
700
  function previewStable(value) {
@@ -1018,12 +1073,18 @@ if (query.has('forgeax-tool-replay')) {
1018
1073
  window.addEventListener('pagehide', () => void host.value.dispose(), { once: true });
1019
1074
  }
1020
1075
  } else {
1076
+ // WebDriver/headless browsers cannot satisfy the trusted-gesture requirement
1077
+ // for pointer lock. Keep the game running and let its keyboard fallback own
1078
+ // camera input; real browser users retain the normal click-to-lock path.
1079
+ const pointerLockAllowed =
1080
+ typeof navigator !== 'undefined' && navigator.webdriver === true ? () => false : undefined;
1021
1081
  const result = await createApp(
1022
1082
  canvas,
1023
1083
  {
1024
1084
  ...(assetCatalog === undefined ? {} : { assetCatalog }),
1025
1085
  ...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
1026
1086
  plugins: [${plugins.join(", ")}],
1087
+ ...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
1027
1088
  },
1028
1089
  bundler,
1029
1090
  );
@@ -1082,10 +1143,32 @@ function htmlSource(title) {
1082
1143
  <div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
1083
1144
  <script>
1084
1145
  (() => {
1146
+ const formatStartupFailure = (reason) => {
1147
+ if (reason instanceof Error) return reason.message;
1148
+ if (reason !== null && typeof reason === 'object') {
1149
+ const record = reason;
1150
+ const lines = [];
1151
+ for (const key of ['code', 'expected', 'hint']) {
1152
+ if (typeof record[key] === 'string' && record[key].length > 0) {
1153
+ lines.push(key + ': ' + record[key]);
1154
+ }
1155
+ }
1156
+ const detail = record.detail;
1157
+ if (detail !== null && typeof detail === 'object') {
1158
+ for (const key of ['reason', 'guid']) {
1159
+ if (typeof detail[key] === 'string' && detail[key].length > 0) {
1160
+ lines.push('detail.' + key + ': ' + detail[key]);
1161
+ }
1162
+ }
1163
+ }
1164
+ if (lines.length > 0) return lines.join('\\n');
1165
+ }
1166
+ return String(reason ?? 'Unknown startup failure');
1167
+ };
1085
1168
  const show = (reason) => {
1086
1169
  const notice = document.querySelector('#forgeax-fatal');
1087
1170
  if (!(notice instanceof HTMLElement)) return;
1088
- const message = reason instanceof Error ? reason.message : String(reason ?? 'Unknown startup failure');
1171
+ const message = formatStartupFailure(reason);
1089
1172
  notice.textContent = 'ForgeaX game failed to start.\\n' + message;
1090
1173
  notice.style.display = 'grid';
1091
1174
  };
@@ -1100,29 +1183,32 @@ function htmlSource(title) {
1100
1183
  }
1101
1184
  async function createViteConfig(facts, command2, base = "/", options = {}) {
1102
1185
  const bootstrapRoot = options.bootstrapRoot ?? "project-bootstrap";
1103
- const canonicalKit = bootstrapRoot === "resource-bootstrap" ? await resolveCanonicalKit() : void 0;
1186
+ const canonicalKit = await resolveCanonicalKit();
1104
1187
  const generated = resolve(facts.root, ".forgeax", "generated");
1105
1188
  await mkdir(generated, { recursive: true });
1189
+ const projectRoots = facts.assetRoots.map((root) => resolve(facts.root, root));
1190
+ const ignorePath = bootstrapRoot === "resource-bootstrap" ? isResourcePreviewIgnoredPath : isProjectSourceIgnoredPath;
1191
+ const builtinPack = await prepareBuiltinPack(projectRoots, generated, ignorePath);
1106
1192
  await Promise.all([
1107
1193
  writeFile(resolve(generated, "index.html"), htmlSource(facts.name)),
1108
1194
  writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
1109
1195
  ]);
1110
1196
  const roots = [
1111
- ...facts.assetRoots.map((root) => resolve(facts.root, root)),
1197
+ ...projectRoots,
1198
+ ...builtinPack === void 0 ? [] : [builtinPack],
1112
1199
  ...canonicalKit === void 0 ? [] : [canonicalKit.root]
1113
1200
  ];
1114
1201
  const importers = [...projectImporters(facts)];
1115
1202
  const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
1116
1203
  const engineWorkspaceResolver = await createEngineWorkspaceResolver();
1204
+ const consumerAliases = await consumerEngineAliases(facts.root);
1117
1205
  const plugins = [
1118
1206
  ...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
1119
1207
  forgeaxShader(),
1120
1208
  pluginPack({
1121
1209
  roots,
1122
- base,
1123
1210
  runtimeBinding,
1124
1211
  ddc: devKitDdcRoots(facts.root),
1125
- scriptablePack: {},
1126
1212
  refresh: command2 === "serve" ? reloadAssetHost() : void 0,
1127
1213
  importers: [
1128
1214
  audioImporter,
@@ -1132,8 +1218,8 @@ async function createViteConfig(facts, command2, base = "/", options = {}) {
1132
1218
  fontImporter,
1133
1219
  ...importers
1134
1220
  ],
1135
- cookers: [createParticleCodeNativeCookerFromRoots(roots)],
1136
- ...bootstrapRoot === "resource-bootstrap" ? { ignorePath: isResourcePreviewIgnoredPath } : { ignorePath: isProjectSourceIgnoredPath }
1221
+ cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
1222
+ ignorePath
1137
1223
  })
1138
1224
  ];
1139
1225
  return {
@@ -1142,7 +1228,10 @@ async function createViteConfig(facts, command2, base = "/", options = {}) {
1142
1228
  configFile: false,
1143
1229
  publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
1144
1230
  plugins,
1145
- resolve: { dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"] },
1231
+ resolve: {
1232
+ alias: consumerAliases,
1233
+ dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"]
1234
+ },
1146
1235
  server: { fs: { allow: [facts.root, ...roots] } },
1147
1236
  build: {
1148
1237
  target: "esnext",
@@ -1694,6 +1783,7 @@ function createInitPlan(facts, sdk) {
1694
1783
  value: {
1695
1784
  root: facts.root,
1696
1785
  version: sdk?.sdkVersion ?? devkitVersion,
1786
+ ...sdk === void 0 ? {} : { pnpmVersion: sdk.requirements.pnpm },
1697
1787
  archiveBacked: sdk !== void 0,
1698
1788
  dependencyChanges,
1699
1789
  scriptChanges
@@ -1714,23 +1804,8 @@ async function applyInitPlan(facts, plan, options) {
1714
1804
  for (const change of plan.scriptChanges) scripts[change.name] = change.to;
1715
1805
  manifest.scripts = scripts;
1716
1806
  if (plan.archiveBacked) {
1717
- manifest.packageManager = "pnpm@10.33.2";
1718
- const pnpmValue = manifest.pnpm;
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
- };
1807
+ if (plan.pnpmVersion === void 0) throw new Error("sdk-pnpm-version-missing");
1808
+ manifest.packageManager = `pnpm@${plan.pnpmVersion}`;
1734
1809
  }
1735
1810
  await writeFile(resolve(facts.root, "package.json"), `${JSON.stringify(manifest, null, 2)}
1736
1811
  `);
@@ -1778,10 +1853,11 @@ async function findSdkContext() {
1778
1853
  );
1779
1854
  const defaultTemplate = manifest.templates.find((template) => template.default);
1780
1855
  if (defaultTemplate === void 0) throw new Error("sdk-default-template-missing");
1856
+ const store = resolve(cursor, "store", "pnpm");
1781
1857
  return {
1782
1858
  root: cursor,
1783
1859
  manifest,
1784
- store: resolve(cursor, "store", "pnpm"),
1860
+ ...await readable(store) ? { store } : {},
1785
1861
  templates,
1786
1862
  defaultTemplate: defaultTemplate.id
1787
1863
  };
@@ -1795,6 +1871,154 @@ var init_sdk = __esm({
1795
1871
  "src/sdk.ts"() {
1796
1872
  }
1797
1873
  });
1874
+ function sdkInitPath(sdk) {
1875
+ return resolve(sdk.root, ...SDK_INIT_PATH);
1876
+ }
1877
+ function commandFailure(code, expected, hint, detail = {}) {
1878
+ return { ok: false, error: { code, expected, hint, detail } };
1879
+ }
1880
+ function sdkProjectInstallArgs(store) {
1881
+ const common = [
1882
+ "install",
1883
+ "--frozen-lockfile",
1884
+ "--ignore-scripts",
1885
+ "--side-effects-cache=true",
1886
+ "--child-concurrency=1"
1887
+ ];
1888
+ return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1889
+ }
1890
+ function sdkBootstrapInstallArgs(store) {
1891
+ const common = [
1892
+ "install",
1893
+ "--frozen-lockfile",
1894
+ "--child-concurrency=1",
1895
+ "--side-effects-cache=true"
1896
+ ];
1897
+ return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1898
+ }
1899
+ function supportedPnpm(version) {
1900
+ const [major = 0, minor = 0] = version.split(".").map(Number);
1901
+ return major === 11 && minor >= 7;
1902
+ }
1903
+ async function currentPnpm() {
1904
+ try {
1905
+ const result = await execFileAsync("pnpm", ["--version"], { maxBuffer: 1024 * 1024 });
1906
+ const version = result.stdout.trim();
1907
+ if (version.length === 0) throw new Error("pnpm returned an empty version");
1908
+ return { ok: true, value: version };
1909
+ } catch (cause) {
1910
+ return commandFailure(
1911
+ "pnpm-unavailable",
1912
+ "pnpm 11.7.0 or newer in the pnpm 11 line to be available on PATH",
1913
+ "Install or activate pnpm 11, then rerun forgeax init from the SDK root.",
1914
+ { reason: cause instanceof Error ? cause.message : String(cause) }
1915
+ );
1916
+ }
1917
+ }
1918
+ function matchesState(sdk, state) {
1919
+ if (state === null || typeof state !== "object" || Array.isArray(state)) return false;
1920
+ const value = state;
1921
+ 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;
1922
+ }
1923
+ async function readSdkInitState(sdk) {
1924
+ try {
1925
+ const value = JSON.parse(await readFile(sdkInitPath(sdk), "utf8"));
1926
+ return matchesState(sdk, value) ? value : void 0;
1927
+ } catch {
1928
+ return void 0;
1929
+ }
1930
+ }
1931
+ async function requireSdkInitialization(sdk) {
1932
+ const state = await readSdkInitState(sdk);
1933
+ if (state === void 0) {
1934
+ return commandFailure(
1935
+ "sdk-not-initialized",
1936
+ "the downloaded SDK to be initialized for this Node/pnpm/platform tuple",
1937
+ "Run ./bin/forgeax init from the SDK root once, then run forgeax new outside the SDK.",
1938
+ {
1939
+ sdkRoot: sdk.root,
1940
+ sdkVersion: sdk.manifest.sdkVersion,
1941
+ pnpm: sdk.manifest.requirements.pnpm,
1942
+ node: process.versions.node,
1943
+ platform: process.platform,
1944
+ arch: process.arch
1945
+ }
1946
+ );
1947
+ }
1948
+ return { ok: true, value: state };
1949
+ }
1950
+ async function copyBootstrapInputs(sdk, root) {
1951
+ const template = sdk.templates.get(sdk.defaultTemplate);
1952
+ if (template === void 0) throw new Error("sdk-default-template-missing");
1953
+ for (const name of ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"]) {
1954
+ await cp(resolve(template, name), resolve(root, name));
1955
+ }
1956
+ try {
1957
+ await cp(resolve(template, ".npmrc"), resolve(root, ".npmrc"));
1958
+ } catch (cause) {
1959
+ if (cause === null || typeof cause !== "object" || !("code" in cause) || cause.code !== "ENOENT") {
1960
+ throw cause;
1961
+ }
1962
+ }
1963
+ }
1964
+ async function sdkInitCommand(sdk, options = {}) {
1965
+ const pnpmResult = await currentPnpm();
1966
+ if (!pnpmResult.ok) return pnpmResult;
1967
+ if (!supportedPnpm(pnpmResult.value)) {
1968
+ return commandFailure(
1969
+ "pnpm-version-unsupported",
1970
+ "pnpm >=11.7.0 <12",
1971
+ "Activate pnpm 11.7.0 or newer in the pnpm 11 line, then rerun forgeax init.",
1972
+ { actual: pnpmResult.value, expected: sdk.manifest.requirements.pnpm }
1973
+ );
1974
+ }
1975
+ const state = {
1976
+ schemaVersion: SDK_INIT_SCHEMA_VERSION,
1977
+ sdkVersion: sdk.manifest.sdkVersion,
1978
+ engineCommit: sdk.manifest.engineCommit,
1979
+ pnpm: pnpmResult.value,
1980
+ node: process.versions.node,
1981
+ platform: process.platform,
1982
+ arch: process.arch
1983
+ };
1984
+ const report = {
1985
+ ...state,
1986
+ root: sdk.root,
1987
+ store: sdk.store === void 0 ? "registry" : "offline"
1988
+ };
1989
+ if (options.dryRun === true || options.install === false) return { ok: true, value: report };
1990
+ let staging;
1991
+ try {
1992
+ staging = await mkdtemp(resolve(tmpdir(), "forgeax-sdk-init-"));
1993
+ await copyBootstrapInputs(sdk, staging);
1994
+ await execFileAsync("pnpm", sdkBootstrapInstallArgs(sdk.store), {
1995
+ cwd: staging,
1996
+ env: { ...process.env, CI: "true" },
1997
+ maxBuffer: 16 * 1024 * 1024
1998
+ });
1999
+ await mkdir(resolve(sdk.root, ".forgeax"), { recursive: true });
2000
+ await writeFile(sdkInitPath(sdk), `${JSON.stringify(state, null, 2)}
2001
+ `);
2002
+ return { ok: true, value: report };
2003
+ } catch (cause) {
2004
+ return commandFailure(
2005
+ "sdk-init-failed",
2006
+ "the SDK dependency closure to install and build native packages successfully",
2007
+ "Inspect the pnpm output, repair Node/pnpm or platform permissions, then rerun forgeax init from the SDK root.",
2008
+ { reason: cause instanceof Error ? cause.message : String(cause), sdkRoot: sdk.root }
2009
+ );
2010
+ } finally {
2011
+ if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2012
+ }
2013
+ }
2014
+ var execFileAsync, SDK_INIT_PATH, SDK_INIT_SCHEMA_VERSION;
2015
+ var init_sdk_bootstrap = __esm({
2016
+ "src/sdk-bootstrap.ts"() {
2017
+ execFileAsync = promisify(execFile);
2018
+ SDK_INIT_PATH = [".forgeax", "sdk-init.json"];
2019
+ SDK_INIT_SCHEMA_VERSION = "1.0.0";
2020
+ }
2021
+ });
1798
2022
  function slash(path) {
1799
2023
  return path.split(sep).join("/");
1800
2024
  }
@@ -2160,6 +2384,20 @@ async function canonicalProspectivePath(path) {
2160
2384
  }
2161
2385
  }
2162
2386
  }
2387
+ async function configureSdkStore(root, store) {
2388
+ if (store === void 0) return;
2389
+ const path = resolve(root, ".npmrc");
2390
+ let content = "";
2391
+ try {
2392
+ content = await readFile(path, "utf8");
2393
+ } catch (cause) {
2394
+ if (!isMissingPathError(cause)) throw cause;
2395
+ }
2396
+ const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*store-dir\s*=/.test(line));
2397
+ lines.push(`store-dir=${store}`);
2398
+ await writeFile(path, `${lines.join("\n")}
2399
+ `);
2400
+ }
2163
2401
  function containsOrEquals(parent, child) {
2164
2402
  const path = relative(parent, child);
2165
2403
  return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
@@ -2170,14 +2408,14 @@ function nodeSupported() {
2170
2408
  }
2171
2409
  function pnpmSupported(version) {
2172
2410
  const [major = 0, minor = 0] = version.split(".").map(Number);
2173
- return major === 10 && minor >= 33;
2411
+ return major === 11 && minor >= 7;
2174
2412
  }
2175
2413
  async function doctorCommand(options = {}) {
2176
2414
  const facts = await readProjectFacts(options.root);
2177
2415
  if (!facts.ok) return facts;
2178
2416
  let pnpm;
2179
2417
  try {
2180
- pnpm = (await execFileAsync("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2418
+ pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2181
2419
  } catch (cause) {
2182
2420
  return {
2183
2421
  ok: false,
@@ -2205,7 +2443,7 @@ async function doctorCommand(options = {}) {
2205
2443
  ok: false,
2206
2444
  error: {
2207
2445
  code: "pnpm-version-unsupported",
2208
- expected: "pnpm >=10.33.0 <11",
2446
+ expected: "pnpm >=11.7.0 <12",
2209
2447
  hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
2210
2448
  detail: { actual: pnpm }
2211
2449
  }
@@ -2244,12 +2482,20 @@ async function doctorCommand(options = {}) {
2244
2482
  };
2245
2483
  }
2246
2484
  async function initCommand(options = {}) {
2247
- const facts = await readProjectFacts(options.root);
2248
- if (!facts.ok) return facts;
2249
2485
  try {
2250
2486
  const sdk = await findSdkContext();
2487
+ const root = await canonicalProspectivePath(options.root ?? process.cwd());
2488
+ if (sdk !== void 0 && root === await canonicalProspectivePath(sdk.root)) {
2489
+ return sdkInitCommand(sdk, options);
2490
+ }
2491
+ const facts = await readProjectFacts(root);
2492
+ if (!facts.ok) return facts;
2251
2493
  const plan = createInitPlan(facts.value, sdk?.manifest);
2252
2494
  if (!plan.ok) return plan;
2495
+ if (sdk !== void 0 && options.dryRun !== true) {
2496
+ const initialized = await requireSdkInitialization(sdk);
2497
+ if (!initialized.ok) return initialized;
2498
+ }
2253
2499
  const applied = await applyInitPlan(facts.value, plan.value, options);
2254
2500
  if (!applied.ok || options.dryRun === true) return applied;
2255
2501
  if (sdk !== void 0) {
@@ -2259,19 +2505,31 @@ async function initCommand(options = {}) {
2259
2505
  resolve(template, "pnpm-lock.yaml"),
2260
2506
  resolve(facts.value.root, "pnpm-lock.yaml")
2261
2507
  );
2508
+ await copyFile(
2509
+ resolve(template, "pnpm-workspace.yaml"),
2510
+ resolve(facts.value.root, "pnpm-workspace.yaml")
2511
+ );
2512
+ try {
2513
+ await readFile(resolve(facts.value.root, ".npmrc"), "utf8");
2514
+ } catch (cause) {
2515
+ if (!isMissingPathError(cause)) throw cause;
2516
+ try {
2517
+ await copyFile(resolve(template, ".npmrc"), resolve(facts.value.root, ".npmrc"));
2518
+ } catch (templateCause) {
2519
+ if (!isMissingPathError(templateCause)) throw templateCause;
2520
+ }
2521
+ }
2262
2522
  await copySdkSkills(sdk, facts.value.root);
2263
2523
  await installProjectSkills(facts.value.root, sdk.manifest);
2524
+ await configureSdkStore(
2525
+ facts.value.root,
2526
+ sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store)
2527
+ );
2264
2528
  }
2265
2529
  if (options.install === false) return applied;
2266
- const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : [
2267
- "install",
2268
- "--offline",
2269
- "--frozen-lockfile",
2270
- "--side-effects-cache=false",
2271
- "--store-dir",
2272
- sdk.store
2273
- ];
2274
- await execFileAsync("pnpm", installArgs, {
2530
+ const store = sdk === void 0 || sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2531
+ const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkProjectInstallArgs(store);
2532
+ await execFileAsync2("pnpm", installArgs, {
2275
2533
  cwd: facts.value.root,
2276
2534
  env: { ...process.env, CI: "true" },
2277
2535
  maxBuffer: 16 * 1024 * 1024
@@ -2349,12 +2607,15 @@ async function newCommand(options = {}) {
2349
2607
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2350
2608
  };
2351
2609
  }
2610
+ const initialized = await requireSdkInitialization(sdk);
2611
+ if (!initialized.ok) return initialized;
2352
2612
  const parent = dirname(root);
2353
2613
  await mkdir(parent, { recursive: true });
2354
2614
  let staging = await mkdtemp(
2355
2615
  resolve(parent, `.${basename(root)}.forgeax-staging-`)
2356
2616
  );
2357
2617
  const committedNames = [];
2618
+ let committed = false;
2358
2619
  try {
2359
2620
  for (const name of await readdir(template)) {
2360
2621
  await cp(resolve(template, name), resolve(staging, name), {
@@ -2365,25 +2626,12 @@ async function newCommand(options = {}) {
2365
2626
  }
2366
2627
  await copySdkSkills(sdk, staging);
2367
2628
  await installProjectSkills(staging, sdk.manifest);
2368
- await execFileAsync(
2369
- "pnpm",
2370
- [
2371
- "install",
2372
- "--offline",
2373
- "--frozen-lockfile",
2374
- "--side-effects-cache=false",
2375
- "--store-dir",
2376
- sdk.store
2377
- ],
2378
- {
2379
- cwd: staging,
2380
- env: { ...process.env, CI: "true" },
2381
- maxBuffer: 16 * 1024 * 1024
2382
- }
2383
- );
2629
+ const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2630
+ await configureSdkStore(staging, store);
2384
2631
  if (!targetExists) {
2385
2632
  await rename(staging, root);
2386
2633
  staging = void 0;
2634
+ committedNames.push(...await readdir(root));
2387
2635
  } else {
2388
2636
  for (const name of await readdir(staging)) {
2389
2637
  await rename(resolve(staging, name), resolve(root, name));
@@ -2392,29 +2640,49 @@ async function newCommand(options = {}) {
2392
2640
  await rm(staging, { recursive: true, force: true });
2393
2641
  staging = void 0;
2394
2642
  }
2643
+ committed = true;
2644
+ await execFileAsync2("pnpm", sdkProjectInstallArgs(store), {
2645
+ cwd: root,
2646
+ env: { ...process.env, CI: "true" },
2647
+ maxBuffer: 16 * 1024 * 1024
2648
+ });
2395
2649
  return {
2396
2650
  ok: true,
2397
2651
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2398
2652
  };
2399
2653
  } catch (cause) {
2400
2654
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2401
- await Promise.all(
2402
- committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2403
- );
2655
+ if (committed) {
2656
+ if (!targetExists) {
2657
+ await rm(root, { recursive: true, force: true });
2658
+ return { ok: false, error: commandError(cause, "project-create-failed") };
2659
+ }
2660
+ const cleanupNames = /* @__PURE__ */ new Set([...committedNames, "node_modules"]);
2661
+ await Promise.all(
2662
+ [...cleanupNames].map(
2663
+ (name) => rm(resolve(root, name), { recursive: true, force: true })
2664
+ )
2665
+ );
2666
+ } else {
2667
+ await Promise.all(
2668
+ committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2669
+ );
2670
+ }
2404
2671
  return { ok: false, error: commandError(cause, "project-create-failed") };
2405
2672
  }
2406
2673
  } catch (cause) {
2407
2674
  return { ok: false, error: commandError(cause, "project-create-failed") };
2408
2675
  }
2409
2676
  }
2410
- var execFileAsync;
2677
+ var execFileAsync2;
2411
2678
  var init_bootstrap_commands = __esm({
2412
2679
  "src/bootstrap-commands.ts"() {
2413
2680
  init_init();
2414
2681
  init_project();
2415
2682
  init_sdk();
2683
+ init_sdk_bootstrap();
2416
2684
  init_skill_install();
2417
- execFileAsync = promisify(execFile);
2685
+ execFileAsync2 = promisify(execFile);
2418
2686
  }
2419
2687
  });
2420
2688
 
@@ -2473,7 +2741,7 @@ function withoutEntry(entries2, id) {
2473
2741
  }
2474
2742
  async function mutateDependency(root, action, dependency) {
2475
2743
  if (dependency === void 0) return;
2476
- await execFileAsync2("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2744
+ await execFileAsync3("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2477
2745
  }
2478
2746
  async function pluginInstallCommand(options) {
2479
2747
  const root = resolve(options.root ?? process.cwd());
@@ -2575,10 +2843,10 @@ async function pluginUninstallCommand(options) {
2575
2843
  };
2576
2844
  }
2577
2845
  }
2578
- var execFileAsync2;
2846
+ var execFileAsync3;
2579
2847
  var init_plugin_authoring = __esm({
2580
2848
  "src/plugin-authoring.ts"() {
2581
- execFileAsync2 = promisify(execFile);
2849
+ execFileAsync3 = promisify(execFile);
2582
2850
  }
2583
2851
  });
2584
2852
  var RhiError;
@@ -3433,7 +3701,7 @@ async function sdkInstallCommand(options) {
3433
3701
  try {
3434
3702
  staging = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-staging-`));
3435
3703
  download = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-download-`));
3436
- await execFileAsync3(
3704
+ await execFileAsync4(
3437
3705
  process.env.FORGEAX_NPM_CLIENT ?? "npm",
3438
3706
  [
3439
3707
  "install",
@@ -3474,11 +3742,11 @@ async function sdkInstallCommand(options) {
3474
3742
  if (download !== void 0) await rm(download, { recursive: true, force: true });
3475
3743
  }
3476
3744
  }
3477
- var execFileAsync3;
3745
+ var execFileAsync4;
3478
3746
  var init_sdk_install = __esm({
3479
3747
  "src/sdk-install.ts"() {
3480
3748
  init_project();
3481
- execFileAsync3 = promisify(execFile);
3749
+ execFileAsync4 = promisify(execFile);
3482
3750
  }
3483
3751
  });
3484
3752
  async function shaderCheckCommand(options = {}) {
@@ -4299,7 +4567,7 @@ __export(contributions_exports, {
4299
4567
  createBuildContribution: () => createBuildContribution,
4300
4568
  createDefaultContributions: () => createDefaultContributions
4301
4569
  });
4302
- function commandFailure(error) {
4570
+ function commandFailure2(error) {
4303
4571
  return { ok: false, error: { ...error, detail: error.detail } };
4304
4572
  }
4305
4573
  function createBuildContribution(projectRoot2 = process.cwd()) {
@@ -4308,7 +4576,7 @@ function createBuildContribution(projectRoot2 = process.cwd()) {
4308
4576
  async (options) => {
4309
4577
  const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
4310
4578
  const result = await buildCommand2({ ...options, root: options.root ?? projectRoot2 });
4311
- return result.ok ? result.value : commandFailure(result.error);
4579
+ return result.ok ? result.value : commandFailure2(result.error);
4312
4580
  }
4313
4581
  );
4314
4582
  }
@@ -4318,7 +4586,7 @@ function createAuthorContribution(projectRoot2 = process.cwd()) {
4318
4586
  async (options) => {
4319
4587
  const { pluginInstallCommand: pluginInstallCommand2 } = await Promise.resolve().then(() => (init_plugin_authoring(), plugin_authoring_exports));
4320
4588
  const result = await pluginInstallCommand2({ ...options, root: options.root ?? projectRoot2 });
4321
- return result.ok ? result.value : commandFailure(result.error);
4589
+ return result.ok ? result.value : commandFailure2(result.error);
4322
4590
  }
4323
4591
  );
4324
4592
  }