specrails-desktop 2.11.2 → 2.11.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.11.2",
3
+ "version": "2.11.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -620,19 +620,42 @@ async function validateCoreContract() {
620
620
  }
621
621
  // ─── SetupManager ─────────────────────────────────────────────────────────────
622
622
  const INSTALL_LOG_BUFFER_MAX = 2000;
623
- function formatBufferedInstallError(baseMessage, logBuffer) {
623
+ /**
624
+ * Persist the FULL install log to `~/.specrails/logs/` and return the path (or
625
+ * null on failure). The in-error tail is only a window; the full log carries the
626
+ * complete child stack — needed because a Node uncaught error prints the ORIGIN
627
+ * frames ABOVE the entry frames, so an 8-line tail shows only the bottom of the
628
+ * stack + the error object, never where it was thrown.
629
+ */
630
+ function persistInstallLog(projectId, logBuffer) {
631
+ try {
632
+ const dir = (0, path_1.join)((0, artifact_registry_1.resolveHome)(), '.specrails', 'logs');
633
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
634
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
635
+ const file = (0, path_1.join)(dir, `setup-${projectId}-${stamp}.log`);
636
+ (0, fs_1.writeFileSync)(file, logBuffer.join('\n') + '\n', { mode: 0o600 });
637
+ return file;
638
+ }
639
+ catch {
640
+ return null;
641
+ }
642
+ }
643
+ function formatBufferedInstallError(baseMessage, logBuffer, logPath) {
644
+ // Show a generous tail: a Node uncaught-exception dump is ~15-30 lines (header,
645
+ // stack frames, the `{errno,code,syscall,path}` object, version footer). 8 lines
646
+ // truncated to just the entry frames + error object, hiding the throw origin.
624
647
  const recentLines = logBuffer
625
648
  .map((line) => line.trim())
626
649
  .filter(Boolean)
627
- .slice(-8);
628
- if (recentLines.length === 0)
629
- return baseMessage;
630
- return [
631
- baseMessage,
632
- '',
633
- 'Recent output:',
634
- ...recentLines.map((line) => `- ${line}`),
635
- ].join('\n');
650
+ .slice(-40);
651
+ const parts = [baseMessage];
652
+ if (recentLines.length > 0) {
653
+ parts.push('', 'Recent output:', ...recentLines.map((line) => `- ${line}`));
654
+ }
655
+ if (logPath) {
656
+ parts.push('', `Full log: ${logPath}`);
657
+ }
658
+ return parts.join('\n');
636
659
  }
637
660
  class SetupManager {
638
661
  _broadcast;
@@ -788,12 +811,20 @@ class SetupManager {
788
811
  const initArgs = hasConfig
789
812
  ? ['--yes', '--from-config', spawnConfigPath ?? configPath]
790
813
  : ['--yes', '--root-dir', projectPath];
814
+ // Seed the install log with a diagnostic header capturing the EXACT spawn
815
+ // (node interpreter, cli entry, cwd) + relevant env. This lands in the
816
+ // failure report so a Windows/packaged path issue (e.g. an EISDIR on the
817
+ // entry realpath) is diagnosable without server-console access.
818
+ const diagHeader = [];
819
+ if (useBundledCore) {
820
+ diagHeader.push(`[diag] node=${(0, path_resolver_1.resolveBundledNodeExe)() ?? process.execPath}`, `[diag] cli=${(0, bundled_core_1.getBundledCoreCli)() ?? '<none>'}`, `[diag] cwd=${projectPath}`, `[diag] args=${initArgs.join(' ')}`, `[diag] SPECRAILS_BUNDLED_RUNTIMES_PATH=${process.env.SPECRAILS_BUNDLED_RUNTIMES_PATH ?? '<unset>'}`, `[diag] SPECRAILS_BUNDLED_CORE_PATH=${process.env.SPECRAILS_BUNDLED_CORE_PATH ?? '<unset>'}`);
821
+ }
791
822
  // Bundled core (offline, node <cli> init) when available, else legacy npx.
792
823
  const child = useBundledCore
793
824
  ? spawnBundledCoreInit(initArgs, projectPath)
794
825
  : spawnCoreInit(initArgs, projectPath);
795
826
  this._installProcesses.set(projectId, child);
796
- this._installLogBuffer.set(projectId, []);
827
+ this._installLogBuffer.set(projectId, diagHeader);
797
828
  // spawnCoreInit uses shell:false on POSIX, so a spawn failure emits 'error'
798
829
  // (and NOT 'close') — without this handler the temp config file leaks and
799
830
  // the unhandled 'error' event would crash the app.
@@ -867,10 +898,11 @@ class SetupManager {
867
898
  }
868
899
  else {
869
900
  const logBuffer = this._installLogBuffer.get(projectId) ?? [];
901
+ const logPath = persistInstallLog(projectId, logBuffer);
870
902
  this._broadcast({
871
903
  type: 'setup_error',
872
904
  projectId,
873
- error: formatBufferedInstallError(`${useBundledCore ? 'bundled specrails-core' : 'npx specrails-core'} exited with code ${code ?? 'unknown'}`, logBuffer),
905
+ error: formatBufferedInstallError(`${useBundledCore ? 'bundled specrails-core' : 'npx specrails-core'} exited with code ${code ?? 'unknown'}`, logBuffer, logPath),
874
906
  });
875
907
  }
876
908
  });