specrails-desktop 2.42.1 → 2.42.2
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/docs/platforms/windows-parity.md +1 -1
- package/package.json +1 -1
- package/server/dist/code-explorer-router.js +1 -1
- package/server/dist/file-provenance.js +15 -10
- package/server/dist/git-diagnostics.js +2 -2
- package/server/dist/pr-publisher.js +1 -1
- package/server/dist/project-git.js +1 -1
- package/server/dist/setup-manager.js +7 -1
- package/server/dist/terminal-manager.js +20 -0
|
@@ -26,7 +26,7 @@ This audit covers the `codex/multi-repo-projects` branch, including mission stee
|
|
|
26
26
|
|
|
27
27
|
## Automated gates
|
|
28
28
|
|
|
29
|
-
`.github/workflows/windows-parity.yml` runs a `windows-parity` job on `windows-latest` (x64) for every
|
|
29
|
+
`.github/workflows/windows-parity.yml` runs a `windows-parity` job on `windows-latest` (x64) for every pull request and push to `main`, adding `windows-11-arm` on the `main` pushes. It installs dependencies, checks TypeScript, runs release/PTY helper regression tests, native filesystem/process tests (retried once, they drive real processes), the application build, native host build/tests, and four real native fixtures: WebView2 capture/selection, authentication popups, mission window handoff, and browser multiwindow transfer/parking. It is deliberately a separate workflow from `ci.yml`: `release.yml` waits for CI only, so a slow or flaky Windows run never delays the release PR, while Desktop Release still builds and installs on Windows before publication. The jsdom client suite is not repeated on Windows; it runs on Linux in CI. The bundled-core matrix in `ci.yml` exercises Windows junction/copy relocation for providers.
|
|
30
30
|
|
|
31
31
|
`.github/workflows/desktop-release.yml` builds the real installers and runs `scripts/smoke-windows-installers.ps1` before publishing artifacts. For each NSIS and MSI package, the script installs into a temporary path containing spaces and drives `scripts/smoke-installed-windows.mjs` with the installed Node runtime. The driver uses an isolated user profile and tests:
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -139,7 +139,7 @@ async function gitIgnoredSet(projectPath, relPaths, maxDurationMs = Number.POSIT
|
|
|
139
139
|
let stdout = '';
|
|
140
140
|
let settled = false;
|
|
141
141
|
let overflow = false;
|
|
142
|
-
const child = (0, child_process_1.spawn)('git', ['check-ignore', '--stdin', '-z'], { cwd: projectPath, stdio: ['pipe', 'pipe', 'ignore'], env: file_provenance_1.
|
|
142
|
+
const child = (0, child_process_1.spawn)('git', ['check-ignore', '--stdin', '-z'], { cwd: projectPath, stdio: ['pipe', 'pipe', 'ignore'], env: (0, file_provenance_1.gitExecEnv)() });
|
|
143
143
|
const finish = (complete) => { if (!settled) {
|
|
144
144
|
settled = true;
|
|
145
145
|
resolve({ stdout, complete: complete && !overflow });
|
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.STORY_EXCERPT_MAX_BYTES =
|
|
6
|
+
exports.STORY_EXCERPT_MAX_BYTES = void 0;
|
|
7
|
+
exports.gitExecEnv = gitExecEnv;
|
|
7
8
|
exports.resolveHeadSha = resolveHeadSha;
|
|
8
9
|
exports.listUntracked = listUntracked;
|
|
9
10
|
exports.snapshotWorkingTree = snapshotWorkingTree;
|
|
@@ -34,9 +35,13 @@ const GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
|
34
35
|
// the on-demand diff patches beyond this cap are skipped (the UI shows "diff
|
|
35
36
|
// unavailable" for them). Mirrors the existing large-job warn threshold (50).
|
|
36
37
|
const MAX_PATCH_FILES = 50;
|
|
37
|
-
// Exported: project-git.ts (the Agent-Mode git bar)
|
|
38
|
-
// git calls and
|
|
39
|
-
|
|
38
|
+
// Exported: project-git.ts (the Agent-Mode git bar), the code explorer and the
|
|
39
|
+
// git diagnostics run the same cwd-scoped git calls and need the identical
|
|
40
|
+
// hostile-repo hardening. Computed PER CALL, never at module load: the startup
|
|
41
|
+
// path resolver prepends the bundled git directory to process.env.PATH after
|
|
42
|
+
// this module was imported, and a snapshot taken earlier could not find git in
|
|
43
|
+
// a packaged app without a system git — every guard then failed closed.
|
|
44
|
+
function gitExecEnv() {
|
|
40
45
|
// Inherit the parent env but STRIP git-location vars. If the app process (or a
|
|
41
46
|
// parent) ever exports GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE, every cwd-scoped
|
|
42
47
|
// git call below would silently operate on that repo instead of the project —
|
|
@@ -61,7 +66,7 @@ exports.GIT_EXEC_ENV = (() => {
|
|
|
61
66
|
env.GIT_CONFIG_NOSYSTEM = '1';
|
|
62
67
|
env.GIT_CONFIG_PARAMETERS = "'core.fsmonitor=false' 'core.hooksPath=/dev/null' 'protocol.ext.allow=user'";
|
|
63
68
|
return env;
|
|
64
|
-
}
|
|
69
|
+
}
|
|
65
70
|
/** Resolve the current HEAD commit SHA. Best-effort: '' on any git failure
|
|
66
71
|
* (no repo, unborn HEAD, no git, timeout). */
|
|
67
72
|
function resolveHeadSha(cwd) {
|
|
@@ -72,7 +77,7 @@ function resolveHeadSha(cwd) {
|
|
|
72
77
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
78
|
timeout: GIT_TIMEOUT_MS,
|
|
74
79
|
maxBuffer: GIT_MAX_BUFFER,
|
|
75
|
-
env:
|
|
80
|
+
env: gitExecEnv(),
|
|
76
81
|
});
|
|
77
82
|
return out.trim();
|
|
78
83
|
}
|
|
@@ -90,7 +95,7 @@ function listUntracked(cwd) {
|
|
|
90
95
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
91
96
|
timeout: GIT_TIMEOUT_MS,
|
|
92
97
|
maxBuffer: GIT_MAX_BUFFER,
|
|
93
|
-
env:
|
|
98
|
+
env: gitExecEnv(),
|
|
94
99
|
});
|
|
95
100
|
return out.split('\0').filter((p) => p.length > 0);
|
|
96
101
|
}
|
|
@@ -107,7 +112,7 @@ function snapshotWorkingTree(cwd) {
|
|
|
107
112
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
113
|
timeout: GIT_TIMEOUT_MS,
|
|
109
114
|
maxBuffer: GIT_MAX_BUFFER,
|
|
110
|
-
env:
|
|
115
|
+
env: gitExecEnv(),
|
|
111
116
|
});
|
|
112
117
|
ref = out.trim();
|
|
113
118
|
}
|
|
@@ -141,7 +146,7 @@ function diffAgainstSnapshot(cwd, snapshotRef, untrackedBefore, baseSha) {
|
|
|
141
146
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
142
147
|
timeout: GIT_TIMEOUT_MS,
|
|
143
148
|
maxBuffer: GIT_MAX_BUFFER,
|
|
144
|
-
env:
|
|
149
|
+
env: gitExecEnv(),
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
152
|
catch {
|
|
@@ -309,7 +314,7 @@ function collectDiffPatches(cwd, snapshotRef, diff, baseSha) {
|
|
|
309
314
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
310
315
|
maxBuffer: MAX_PATCH_BYTES + 64 * 1024,
|
|
311
316
|
timeout: GIT_TIMEOUT_MS,
|
|
312
|
-
env:
|
|
317
|
+
env: gitExecEnv(),
|
|
313
318
|
});
|
|
314
319
|
}
|
|
315
320
|
catch (err) {
|
|
@@ -17,7 +17,7 @@ const win_spawn_1 = require("./util/win-spawn");
|
|
|
17
17
|
* SAFETY: the command list is a FIXED ALLOWLIST — the caller only picks an
|
|
18
18
|
* action name, never arguments, so no flag/ref/path can be smuggled in. Every
|
|
19
19
|
* command is read-only (no push/commit/checkout/pr-create). git runs with the
|
|
20
|
-
* hardened `
|
|
20
|
+
* hardened `gitExecEnv()` (hostile-repo config stripped, prompts disabled); gh
|
|
21
21
|
* runs with the normal env so it finds the user's `~/.config/gh` auth and the
|
|
22
22
|
* bundled `gh` on PATH.
|
|
23
23
|
*/
|
|
@@ -43,7 +43,7 @@ const OUTPUT_CAP = 8_000;
|
|
|
43
43
|
const defaultExec = (cmd, args, cwd) => new Promise((resolve) => {
|
|
44
44
|
// git: hardened, cwd-pinned env. gh: user env (needs ~/.config/gh auth) with
|
|
45
45
|
// the Windows shell-critical vars guaranteed present.
|
|
46
|
-
const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : file_provenance_1.
|
|
46
|
+
const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : (0, file_provenance_1.gitExecEnv)();
|
|
47
47
|
(0, child_process_1.execFile)(cmd, args, { cwd, env, timeout: 15_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
|
|
48
48
|
const code = err && typeof err.code === 'number'
|
|
49
49
|
? err.code
|
|
@@ -35,7 +35,7 @@ function createBoundedExec(timeoutMs = exports.PR_COMMAND_TIMEOUT_MS) {
|
|
|
35
35
|
// `windowsSpawnEnv()` backfills SystemRoot / ComSpec / USERPROFILE, which a
|
|
36
36
|
// GUI-launched / pkg-stripped Windows sidecar can lack — without them the
|
|
37
37
|
// `git`/`gh` child (and the `sh -c` the `!gh …` credential helper runs) can
|
|
38
|
-
// fail to start. NOT `
|
|
38
|
+
// fail to start. NOT `gitExecEnv()`: that hardened env disables credentials
|
|
39
39
|
// (GIT_ASKPASS=echo / GIT_TERMINAL_PROMPT=0), which the push must keep. No-op
|
|
40
40
|
// on POSIX (returns process.env), so mac/Linux behaviour is byte-identical.
|
|
41
41
|
const env = (0, win_spawn_1.windowsSpawnEnv)();
|
|
@@ -21,7 +21,7 @@ const GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
|
21
21
|
const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
22
22
|
function git(repoDir, args) {
|
|
23
23
|
return new Promise((resolve, reject) => {
|
|
24
|
-
(0, child_process_1.execFile)('git', args, { cwd: repoDir, env: file_provenance_1.
|
|
24
|
+
(0, child_process_1.execFile)('git', args, { cwd: repoDir, env: (0, file_provenance_1.gitExecEnv)(), timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_MAX_BUFFER, windowsHide: true }, (err, stdout, stderr) => {
|
|
25
25
|
if (err) {
|
|
26
26
|
const detail = String(stderr || err.message).trim();
|
|
27
27
|
reject(new Error(detail));
|
|
@@ -772,7 +772,13 @@ class SetupManager {
|
|
|
772
772
|
providers: list,
|
|
773
773
|
continueOnError: true,
|
|
774
774
|
onProviderStart: (p) => emit(p, 'running'),
|
|
775
|
-
onProviderResult: (r) =>
|
|
775
|
+
onProviderResult: (r) => {
|
|
776
|
+
// The WS event carries the reason to the app; the server log must too,
|
|
777
|
+
// or a packaged-app failure (installer smoke, support bundle) is blind.
|
|
778
|
+
if (!r.ok)
|
|
779
|
+
console.warn(`[SetupManager] silent assemble: ${r.provider} failed for ${projectId}: ${r.error ?? 'unknown error'}`);
|
|
780
|
+
emit(r.provider, r.ok ? 'done' : 'failed', r.error);
|
|
781
|
+
},
|
|
776
782
|
})
|
|
777
783
|
.then((results) => {
|
|
778
784
|
const failed = results.filter((r) => !r.ok).map((r) => r.provider);
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TerminalManager = exports.TERMINAL_EARLY_EXIT_MS = exports.TerminalSpawnError = exports.TerminalNameInvalidError = exports.TerminalNotFoundError = exports.TerminalLimitExceededError = exports.RingBuffer = exports.TERMINAL_DEFAULT_ROWS = exports.TERMINAL_DEFAULT_COLS = exports.TERMINAL_NAME_MAX = exports.TERMINAL_MAX_PER_PROJECT = exports.TERMINAL_KILL_GRACE_MS = exports.TERMINAL_SCROLLBACK_BYTES = void 0;
|
|
7
|
+
exports.killTerminalTree = killTerminalTree;
|
|
7
8
|
exports.resolveShell = resolveShell;
|
|
8
9
|
exports.resolveShellFor = resolveShellFor;
|
|
9
10
|
exports.shellArgs = shellArgs;
|
|
@@ -20,6 +21,21 @@ const terminal_marks_store_1 = require("./terminal-marks-store");
|
|
|
20
21
|
const terminal_shell_integration_1 = require("./terminal-shell-integration");
|
|
21
22
|
exports.TERMINAL_SCROLLBACK_BYTES = 262_144;
|
|
22
23
|
exports.TERMINAL_KILL_GRACE_MS = 2_000;
|
|
24
|
+
/**
|
|
25
|
+
* Windows: node-pty ends a ConPTY session by enumerating the pseudo console's
|
|
26
|
+
* processes through a helper it starts with `process.execPath`. In the
|
|
27
|
+
* packaged sidecar that path is the sidecar binary itself, so the helper never
|
|
28
|
+
* runs, only the shell is terminated and whatever the shell started (a dev
|
|
29
|
+
* server, a watcher) survives the closed terminal. Kill the shell's process
|
|
30
|
+
* tree ourselves (`taskkill /T /F` through tree-kill). No-op elsewhere: the
|
|
31
|
+
* POSIX pty closes its process group.
|
|
32
|
+
*/
|
|
33
|
+
function killTerminalTree(pid, platform = process.platform, kill = win_spawn_1.treeKillSafe) {
|
|
34
|
+
if (platform !== 'win32' || !pid || !Number.isInteger(pid) || pid <= 0)
|
|
35
|
+
return false;
|
|
36
|
+
kill(pid, 'SIGKILL');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
23
39
|
exports.TERMINAL_MAX_PER_PROJECT = 10;
|
|
24
40
|
exports.TERMINAL_NAME_MAX = 64;
|
|
25
41
|
exports.TERMINAL_DEFAULT_COLS = 80;
|
|
@@ -455,6 +471,7 @@ class TerminalManager {
|
|
|
455
471
|
async shutdown() {
|
|
456
472
|
const all = Array.from(this.sessions.values());
|
|
457
473
|
for (const s of all) {
|
|
474
|
+
killTerminalTree(s.pty.pid);
|
|
458
475
|
try {
|
|
459
476
|
s.pty.kill('SIGTERM');
|
|
460
477
|
}
|
|
@@ -462,6 +479,7 @@ class TerminalManager {
|
|
|
462
479
|
}
|
|
463
480
|
await new Promise((r) => setTimeout(r, exports.TERMINAL_KILL_GRACE_MS));
|
|
464
481
|
for (const s of Array.from(this.sessions.values())) {
|
|
482
|
+
killTerminalTree(s.pty.pid);
|
|
465
483
|
try {
|
|
466
484
|
s.pty.kill('SIGKILL');
|
|
467
485
|
}
|
|
@@ -540,12 +558,14 @@ class TerminalManager {
|
|
|
540
558
|
return;
|
|
541
559
|
this.detachFromRegistry(s);
|
|
542
560
|
if (!s.exited) {
|
|
561
|
+
killTerminalTree(s.pty.pid);
|
|
543
562
|
try {
|
|
544
563
|
s.pty.kill('SIGTERM');
|
|
545
564
|
}
|
|
546
565
|
catch { /* ignore */ }
|
|
547
566
|
s.killTimer = setTimeout(() => {
|
|
548
567
|
if (!s.exited) {
|
|
568
|
+
killTerminalTree(s.pty.pid);
|
|
549
569
|
try {
|
|
550
570
|
s.pty.kill('SIGKILL');
|
|
551
571
|
}
|