@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/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
  `);
@@ -1796,6 +1871,154 @@ var init_sdk = __esm({
1796
1871
  "src/sdk.ts"() {
1797
1872
  }
1798
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
+ });
1799
2022
  function slash(path) {
1800
2023
  return path.split(sep).join("/");
1801
2024
  }
@@ -2161,6 +2384,20 @@ async function canonicalProspectivePath(path) {
2161
2384
  }
2162
2385
  }
2163
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
+ }
2164
2401
  function containsOrEquals(parent, child) {
2165
2402
  const path = relative(parent, child);
2166
2403
  return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
@@ -2171,25 +2408,14 @@ function nodeSupported() {
2171
2408
  }
2172
2409
  function pnpmSupported(version) {
2173
2410
  const [major = 0, minor = 0] = version.split(".").map(Number);
2174
- return major === 10 && minor >= 33;
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
- ];
2411
+ return major === 11 && minor >= 7;
2186
2412
  }
2187
2413
  async function doctorCommand(options = {}) {
2188
2414
  const facts = await readProjectFacts(options.root);
2189
2415
  if (!facts.ok) return facts;
2190
2416
  let pnpm;
2191
2417
  try {
2192
- pnpm = (await execFileAsync("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2418
+ pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2193
2419
  } catch (cause) {
2194
2420
  return {
2195
2421
  ok: false,
@@ -2217,7 +2443,7 @@ async function doctorCommand(options = {}) {
2217
2443
  ok: false,
2218
2444
  error: {
2219
2445
  code: "pnpm-version-unsupported",
2220
- expected: "pnpm >=10.33.0 <11",
2446
+ expected: "pnpm >=11.7.0 <12",
2221
2447
  hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
2222
2448
  detail: { actual: pnpm }
2223
2449
  }
@@ -2256,12 +2482,20 @@ async function doctorCommand(options = {}) {
2256
2482
  };
2257
2483
  }
2258
2484
  async function initCommand(options = {}) {
2259
- const facts = await readProjectFacts(options.root);
2260
- if (!facts.ok) return facts;
2261
2485
  try {
2262
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;
2263
2493
  const plan = createInitPlan(facts.value, sdk?.manifest);
2264
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
+ }
2265
2499
  const applied = await applyInitPlan(facts.value, plan.value, options);
2266
2500
  if (!applied.ok || options.dryRun === true) return applied;
2267
2501
  if (sdk !== void 0) {
@@ -2271,12 +2505,31 @@ async function initCommand(options = {}) {
2271
2505
  resolve(template, "pnpm-lock.yaml"),
2272
2506
  resolve(facts.value.root, "pnpm-lock.yaml")
2273
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
+ }
2274
2522
  await copySdkSkills(sdk, facts.value.root);
2275
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
+ );
2276
2528
  }
2277
2529
  if (options.install === false) return applied;
2278
- const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkInstallArgs(sdk.store);
2279
- 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, {
2280
2533
  cwd: facts.value.root,
2281
2534
  env: { ...process.env, CI: "true" },
2282
2535
  maxBuffer: 16 * 1024 * 1024
@@ -2354,12 +2607,15 @@ async function newCommand(options = {}) {
2354
2607
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2355
2608
  };
2356
2609
  }
2610
+ const initialized = await requireSdkInitialization(sdk);
2611
+ if (!initialized.ok) return initialized;
2357
2612
  const parent = dirname(root);
2358
2613
  await mkdir(parent, { recursive: true });
2359
2614
  let staging = await mkdtemp(
2360
2615
  resolve(parent, `.${basename(root)}.forgeax-staging-`)
2361
2616
  );
2362
2617
  const committedNames = [];
2618
+ let committed = false;
2363
2619
  try {
2364
2620
  for (const name of await readdir(template)) {
2365
2621
  await cp(resolve(template, name), resolve(staging, name), {
@@ -2370,14 +2626,12 @@ async function newCommand(options = {}) {
2370
2626
  }
2371
2627
  await copySdkSkills(sdk, staging);
2372
2628
  await installProjectSkills(staging, sdk.manifest);
2373
- await execFileAsync("pnpm", sdkInstallArgs(sdk.store), {
2374
- cwd: staging,
2375
- env: { ...process.env, CI: "true" },
2376
- maxBuffer: 16 * 1024 * 1024
2377
- });
2629
+ const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2630
+ await configureSdkStore(staging, store);
2378
2631
  if (!targetExists) {
2379
2632
  await rename(staging, root);
2380
2633
  staging = void 0;
2634
+ committedNames.push(...await readdir(root));
2381
2635
  } else {
2382
2636
  for (const name of await readdir(staging)) {
2383
2637
  await rename(resolve(staging, name), resolve(root, name));
@@ -2386,29 +2640,49 @@ async function newCommand(options = {}) {
2386
2640
  await rm(staging, { recursive: true, force: true });
2387
2641
  staging = void 0;
2388
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
+ });
2389
2649
  return {
2390
2650
  ok: true,
2391
2651
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2392
2652
  };
2393
2653
  } catch (cause) {
2394
2654
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2395
- await Promise.all(
2396
- committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2397
- );
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
+ }
2398
2671
  return { ok: false, error: commandError(cause, "project-create-failed") };
2399
2672
  }
2400
2673
  } catch (cause) {
2401
2674
  return { ok: false, error: commandError(cause, "project-create-failed") };
2402
2675
  }
2403
2676
  }
2404
- var execFileAsync;
2677
+ var execFileAsync2;
2405
2678
  var init_bootstrap_commands = __esm({
2406
2679
  "src/bootstrap-commands.ts"() {
2407
2680
  init_init();
2408
2681
  init_project();
2409
2682
  init_sdk();
2683
+ init_sdk_bootstrap();
2410
2684
  init_skill_install();
2411
- execFileAsync = promisify(execFile);
2685
+ execFileAsync2 = promisify(execFile);
2412
2686
  }
2413
2687
  });
2414
2688
 
@@ -2467,7 +2741,7 @@ function withoutEntry(entries2, id) {
2467
2741
  }
2468
2742
  async function mutateDependency(root, action, dependency) {
2469
2743
  if (dependency === void 0) return;
2470
- await execFileAsync2("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2744
+ await execFileAsync3("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2471
2745
  }
2472
2746
  async function pluginInstallCommand(options) {
2473
2747
  const root = resolve(options.root ?? process.cwd());
@@ -2569,10 +2843,10 @@ async function pluginUninstallCommand(options) {
2569
2843
  };
2570
2844
  }
2571
2845
  }
2572
- var execFileAsync2;
2846
+ var execFileAsync3;
2573
2847
  var init_plugin_authoring = __esm({
2574
2848
  "src/plugin-authoring.ts"() {
2575
- execFileAsync2 = promisify(execFile);
2849
+ execFileAsync3 = promisify(execFile);
2576
2850
  }
2577
2851
  });
2578
2852
  var RhiError;
@@ -3427,7 +3701,7 @@ async function sdkInstallCommand(options) {
3427
3701
  try {
3428
3702
  staging = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-staging-`));
3429
3703
  download = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-download-`));
3430
- await execFileAsync3(
3704
+ await execFileAsync4(
3431
3705
  process.env.FORGEAX_NPM_CLIENT ?? "npm",
3432
3706
  [
3433
3707
  "install",
@@ -3468,11 +3742,11 @@ async function sdkInstallCommand(options) {
3468
3742
  if (download !== void 0) await rm(download, { recursive: true, force: true });
3469
3743
  }
3470
3744
  }
3471
- var execFileAsync3;
3745
+ var execFileAsync4;
3472
3746
  var init_sdk_install = __esm({
3473
3747
  "src/sdk-install.ts"() {
3474
3748
  init_project();
3475
- execFileAsync3 = promisify(execFile);
3749
+ execFileAsync4 = promisify(execFile);
3476
3750
  }
3477
3751
  });
3478
3752
  async function shaderCheckCommand(options = {}) {
@@ -4293,7 +4567,7 @@ __export(contributions_exports, {
4293
4567
  createBuildContribution: () => createBuildContribution,
4294
4568
  createDefaultContributions: () => createDefaultContributions
4295
4569
  });
4296
- function commandFailure(error) {
4570
+ function commandFailure2(error) {
4297
4571
  return { ok: false, error: { ...error, detail: error.detail } };
4298
4572
  }
4299
4573
  function createBuildContribution(projectRoot2 = process.cwd()) {
@@ -4302,7 +4576,7 @@ function createBuildContribution(projectRoot2 = process.cwd()) {
4302
4576
  async (options) => {
4303
4577
  const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
4304
4578
  const result = await buildCommand2({ ...options, root: options.root ?? projectRoot2 });
4305
- return result.ok ? result.value : commandFailure(result.error);
4579
+ return result.ok ? result.value : commandFailure2(result.error);
4306
4580
  }
4307
4581
  );
4308
4582
  }
@@ -4312,7 +4586,7 @@ function createAuthorContribution(projectRoot2 = process.cwd()) {
4312
4586
  async (options) => {
4313
4587
  const { pluginInstallCommand: pluginInstallCommand2 } = await Promise.resolve().then(() => (init_plugin_authoring(), plugin_authoring_exports));
4314
4588
  const result = await pluginInstallCommand2({ ...options, root: options.root ?? projectRoot2 });
4315
- return result.ok ? result.value : commandFailure(result.error);
4589
+ return result.ok ? result.value : commandFailure2(result.error);
4316
4590
  }
4317
4591
  );
4318
4592
  }