@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/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
  `);
@@ -1782,10 +1857,11 @@ async function findSdkContext() {
1782
1857
  );
1783
1858
  const defaultTemplate = manifest.templates.find((template) => template.default);
1784
1859
  if (defaultTemplate === void 0) throw new Error("sdk-default-template-missing");
1860
+ const store = resolve(cursor, "store", "pnpm");
1785
1861
  return {
1786
1862
  root: cursor,
1787
1863
  manifest,
1788
- store: resolve(cursor, "store", "pnpm"),
1864
+ ...await readable(store) ? { store } : {},
1789
1865
  templates,
1790
1866
  defaultTemplate: defaultTemplate.id
1791
1867
  };
@@ -1799,6 +1875,154 @@ var init_sdk = __esm({
1799
1875
  "src/sdk.ts"() {
1800
1876
  }
1801
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
+ });
1802
2026
  function slash(path) {
1803
2027
  return path.split(sep).join("/");
1804
2028
  }
@@ -2164,6 +2388,20 @@ async function canonicalProspectivePath(path) {
2164
2388
  }
2165
2389
  }
2166
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
+ }
2167
2405
  function containsOrEquals(parent, child) {
2168
2406
  const path = relative(parent, child);
2169
2407
  return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
@@ -2174,14 +2412,14 @@ function nodeSupported() {
2174
2412
  }
2175
2413
  function pnpmSupported(version) {
2176
2414
  const [major = 0, minor = 0] = version.split(".").map(Number);
2177
- return major === 10 && minor >= 33;
2415
+ return major === 11 && minor >= 7;
2178
2416
  }
2179
2417
  async function doctorCommand(options = {}) {
2180
2418
  const facts = await readProjectFacts(options.root);
2181
2419
  if (!facts.ok) return facts;
2182
2420
  let pnpm;
2183
2421
  try {
2184
- pnpm = (await execFileAsync("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2422
+ pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
2185
2423
  } catch (cause) {
2186
2424
  return {
2187
2425
  ok: false,
@@ -2209,7 +2447,7 @@ async function doctorCommand(options = {}) {
2209
2447
  ok: false,
2210
2448
  error: {
2211
2449
  code: "pnpm-version-unsupported",
2212
- expected: "pnpm >=10.33.0 <11",
2450
+ expected: "pnpm >=11.7.0 <12",
2213
2451
  hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
2214
2452
  detail: { actual: pnpm }
2215
2453
  }
@@ -2248,12 +2486,20 @@ async function doctorCommand(options = {}) {
2248
2486
  };
2249
2487
  }
2250
2488
  async function initCommand(options = {}) {
2251
- const facts = await readProjectFacts(options.root);
2252
- if (!facts.ok) return facts;
2253
2489
  try {
2254
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;
2255
2497
  const plan = createInitPlan(facts.value, sdk?.manifest);
2256
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
+ }
2257
2503
  const applied = await applyInitPlan(facts.value, plan.value, options);
2258
2504
  if (!applied.ok || options.dryRun === true) return applied;
2259
2505
  if (sdk !== void 0) {
@@ -2263,19 +2509,31 @@ async function initCommand(options = {}) {
2263
2509
  resolve(template, "pnpm-lock.yaml"),
2264
2510
  resolve(facts.value.root, "pnpm-lock.yaml")
2265
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
+ }
2266
2526
  await copySdkSkills(sdk, facts.value.root);
2267
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
+ );
2268
2532
  }
2269
2533
  if (options.install === false) return applied;
2270
- const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : [
2271
- "install",
2272
- "--offline",
2273
- "--frozen-lockfile",
2274
- "--side-effects-cache=false",
2275
- "--store-dir",
2276
- sdk.store
2277
- ];
2278
- 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, {
2279
2537
  cwd: facts.value.root,
2280
2538
  env: { ...process.env, CI: "true" },
2281
2539
  maxBuffer: 16 * 1024 * 1024
@@ -2353,12 +2611,15 @@ async function newCommand(options = {}) {
2353
2611
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2354
2612
  };
2355
2613
  }
2614
+ const initialized = await requireSdkInitialization(sdk);
2615
+ if (!initialized.ok) return initialized;
2356
2616
  const parent = dirname(root);
2357
2617
  await mkdir(parent, { recursive: true });
2358
2618
  let staging = await mkdtemp(
2359
2619
  resolve(parent, `.${basename(root)}.forgeax-staging-`)
2360
2620
  );
2361
2621
  const committedNames = [];
2622
+ let committed = false;
2362
2623
  try {
2363
2624
  for (const name of await readdir(template)) {
2364
2625
  await cp(resolve(template, name), resolve(staging, name), {
@@ -2369,25 +2630,12 @@ async function newCommand(options = {}) {
2369
2630
  }
2370
2631
  await copySdkSkills(sdk, staging);
2371
2632
  await installProjectSkills(staging, sdk.manifest);
2372
- await execFileAsync(
2373
- "pnpm",
2374
- [
2375
- "install",
2376
- "--offline",
2377
- "--frozen-lockfile",
2378
- "--side-effects-cache=false",
2379
- "--store-dir",
2380
- sdk.store
2381
- ],
2382
- {
2383
- cwd: staging,
2384
- env: { ...process.env, CI: "true" },
2385
- maxBuffer: 16 * 1024 * 1024
2386
- }
2387
- );
2633
+ const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
2634
+ await configureSdkStore(staging, store);
2388
2635
  if (!targetExists) {
2389
2636
  await rename(staging, root);
2390
2637
  staging = void 0;
2638
+ committedNames.push(...await readdir(root));
2391
2639
  } else {
2392
2640
  for (const name of await readdir(staging)) {
2393
2641
  await rename(resolve(staging, name), resolve(root, name));
@@ -2396,29 +2644,49 @@ async function newCommand(options = {}) {
2396
2644
  await rm(staging, { recursive: true, force: true });
2397
2645
  staging = void 0;
2398
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
+ });
2399
2653
  return {
2400
2654
  ok: true,
2401
2655
  value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2402
2656
  };
2403
2657
  } catch (cause) {
2404
2658
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
2405
- await Promise.all(
2406
- committedNames.map((name) => rm(resolve(root, name), { recursive: true, force: true }))
2407
- );
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
+ }
2408
2675
  return { ok: false, error: commandError(cause, "project-create-failed") };
2409
2676
  }
2410
2677
  } catch (cause) {
2411
2678
  return { ok: false, error: commandError(cause, "project-create-failed") };
2412
2679
  }
2413
2680
  }
2414
- var execFileAsync;
2681
+ var execFileAsync2;
2415
2682
  var init_bootstrap_commands = __esm({
2416
2683
  "src/bootstrap-commands.ts"() {
2417
2684
  init_init();
2418
2685
  init_project();
2419
2686
  init_sdk();
2687
+ init_sdk_bootstrap();
2420
2688
  init_skill_install();
2421
- execFileAsync = promisify(execFile);
2689
+ execFileAsync2 = promisify(execFile);
2422
2690
  }
2423
2691
  });
2424
2692
 
@@ -2477,7 +2745,7 @@ function withoutEntry(entries2, id) {
2477
2745
  }
2478
2746
  async function mutateDependency(root, action, dependency) {
2479
2747
  if (dependency === void 0) return;
2480
- await execFileAsync2("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2748
+ await execFileAsync3("pnpm", [action, dependency], { cwd: root, maxBuffer: 16 * 1024 * 1024 });
2481
2749
  }
2482
2750
  async function pluginInstallCommand(options) {
2483
2751
  const root = resolve(options.root ?? process.cwd());
@@ -2579,10 +2847,10 @@ async function pluginUninstallCommand(options) {
2579
2847
  };
2580
2848
  }
2581
2849
  }
2582
- var execFileAsync2;
2850
+ var execFileAsync3;
2583
2851
  var init_plugin_authoring = __esm({
2584
2852
  "src/plugin-authoring.ts"() {
2585
- execFileAsync2 = promisify(execFile);
2853
+ execFileAsync3 = promisify(execFile);
2586
2854
  }
2587
2855
  });
2588
2856
  var RhiError;
@@ -3437,7 +3705,7 @@ async function sdkInstallCommand(options) {
3437
3705
  try {
3438
3706
  staging = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-staging-`));
3439
3707
  download = await mkdtemp(resolve(parent, `.${basename(root)}.forgeax-sdk-download-`));
3440
- await execFileAsync3(
3708
+ await execFileAsync4(
3441
3709
  process.env.FORGEAX_NPM_CLIENT ?? "npm",
3442
3710
  [
3443
3711
  "install",
@@ -3478,11 +3746,11 @@ async function sdkInstallCommand(options) {
3478
3746
  if (download !== void 0) await rm(download, { recursive: true, force: true });
3479
3747
  }
3480
3748
  }
3481
- var execFileAsync3;
3749
+ var execFileAsync4;
3482
3750
  var init_sdk_install = __esm({
3483
3751
  "src/sdk-install.ts"() {
3484
3752
  init_project();
3485
- execFileAsync3 = promisify(execFile);
3753
+ execFileAsync4 = promisify(execFile);
3486
3754
  }
3487
3755
  });
3488
3756
  async function shaderCheckCommand(options = {}) {
@@ -4325,7 +4593,7 @@ __export(contributions_exports, {
4325
4593
  createBuildContribution: () => createBuildContribution,
4326
4594
  createDefaultContributions: () => createDefaultContributions
4327
4595
  });
4328
- function commandFailure(error) {
4596
+ function commandFailure2(error) {
4329
4597
  return { ok: false, error: { ...error, detail: error.detail } };
4330
4598
  }
4331
4599
  function createBuildContribution(projectRoot = process.cwd()) {
@@ -4334,7 +4602,7 @@ function createBuildContribution(projectRoot = process.cwd()) {
4334
4602
  async (options) => {
4335
4603
  const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
4336
4604
  const result = await buildCommand2({ ...options, root: options.root ?? projectRoot });
4337
- return result.ok ? result.value : commandFailure(result.error);
4605
+ return result.ok ? result.value : commandFailure2(result.error);
4338
4606
  }
4339
4607
  );
4340
4608
  }
@@ -4344,7 +4612,7 @@ function createAuthorContribution(projectRoot = process.cwd()) {
4344
4612
  async (options) => {
4345
4613
  const { pluginInstallCommand: pluginInstallCommand2 } = await Promise.resolve().then(() => (init_plugin_authoring(), plugin_authoring_exports));
4346
4614
  const result = await pluginInstallCommand2({ ...options, root: options.root ?? projectRoot });
4347
- return result.ok ? result.value : commandFailure(result.error);
4615
+ return result.ok ? result.value : commandFailure2(result.error);
4348
4616
  }
4349
4617
  );
4350
4618
  }
@@ -4733,14 +5001,14 @@ async function createCarrierProviderService(options) {
4733
5001
  );
4734
5002
  }
4735
5003
  });
4736
- await new Promise((resolve15, reject) => {
5004
+ await new Promise((resolve16, reject) => {
4737
5005
  const onError = (error) => {
4738
5006
  server.off("listening", onListening);
4739
5007
  reject(error);
4740
5008
  };
4741
5009
  const onListening = () => {
4742
5010
  server.off("error", onError);
4743
- resolve15();
5011
+ resolve16();
4744
5012
  };
4745
5013
  server.once("error", onError);
4746
5014
  server.once("listening", onListening);
@@ -4758,7 +5026,7 @@ async function createCarrierProviderService(options) {
4758
5026
  if (closed) return;
4759
5027
  closed = true;
4760
5028
  await new Promise(
4761
- (resolve15, reject) => server.close((error) => error === void 0 ? resolve15() : reject(error))
5029
+ (resolve16, reject) => server.close((error) => error === void 0 ? resolve16() : reject(error))
4762
5030
  );
4763
5031
  }
4764
5032
  };
@@ -5052,14 +5320,14 @@ function projectUri(projectRoot, path) {
5052
5320
  async function allocateLoopbackPort() {
5053
5321
  const probe = createServer$2();
5054
5322
  try {
5055
- await new Promise((resolve15, reject) => {
5323
+ await new Promise((resolve16, reject) => {
5056
5324
  const onError = (error) => {
5057
5325
  probe.off("listening", onListening);
5058
5326
  reject(error);
5059
5327
  };
5060
5328
  const onListening = () => {
5061
5329
  probe.off("error", onError);
5062
- resolve15();
5330
+ resolve16();
5063
5331
  };
5064
5332
  probe.once("error", onError);
5065
5333
  probe.once("listening", onListening);
@@ -5072,7 +5340,7 @@ async function allocateLoopbackPort() {
5072
5340
  return address.port;
5073
5341
  } finally {
5074
5342
  if (probe.listening) {
5075
- await new Promise((resolve15) => probe.close(() => resolve15()));
5343
+ await new Promise((resolve16) => probe.close(() => resolve16()));
5076
5344
  }
5077
5345
  }
5078
5346
  }
@@ -5719,14 +5987,14 @@ async function createAuthenticatedLoopbackService(options) {
5719
5987
  reply2(response, 400, { error: message });
5720
5988
  }
5721
5989
  });
5722
- await new Promise((resolve15, reject) => {
5990
+ await new Promise((resolve16, reject) => {
5723
5991
  const onError = (error) => {
5724
5992
  server.off("listening", onListening);
5725
5993
  reject(error);
5726
5994
  };
5727
5995
  const onListening = () => {
5728
5996
  server.off("error", onError);
5729
- resolve15();
5997
+ resolve16();
5730
5998
  };
5731
5999
  server.once("error", onError);
5732
6000
  server.once("listening", onListening);
@@ -5734,7 +6002,7 @@ async function createAuthenticatedLoopbackService(options) {
5734
6002
  });
5735
6003
  const address = server.address();
5736
6004
  if (address === null || typeof address === "string") {
5737
- await new Promise((resolve15) => server.close(() => resolve15()));
6005
+ await new Promise((resolve16) => server.close(() => resolve16()));
5738
6006
  throw new Error("loopback service did not expose a TCP address");
5739
6007
  }
5740
6008
  const endpoint = `http://${host}:${address.port}/run`;
@@ -5749,8 +6017,8 @@ async function createAuthenticatedLoopbackService(options) {
5749
6017
  async close() {
5750
6018
  if (closed) return;
5751
6019
  closed = true;
5752
- await new Promise((resolve15, reject) => {
5753
- 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));
5754
6022
  });
5755
6023
  transport.close();
5756
6024
  }